LUA för nybörjare - Del 2.1

Post Reply
User avatar
Bamsefar
Z-Wave Kung
Posts: 1230
Joined: 25 Nov 2013, 15:06
10
Location: Stockholm

Variabler - vilka som finns och hur man kan nyttja dom?

Efter att flera frågat om rätt liknande utmaningar, så inser jag att en litet försök till att bistå i vad en variabel är och hur man kan skapa/nyttja dom är nog en rätt bra sak. I samband med detta kommer jag också som hastigast täcka in Listor - och med Listor syftar jag på vad man ibland refererar till vektorer (fast i LUA påminner det mera om indexerade matriser - men mer om det senare).

I förra tråden, LUA för nybörjare - Del 1, så gick jag som enklast igenom en enklare scen och hur de kan skapas. Jag förutsätter således att du läst igenom den tråden, och kanske börjar känna dig nyfiken på lite mera avancerade tankar och idéer.

Variabler finns det, i LUA och Fibaro HomeCenter 2, två varianter av:
- Lokala enbart i EN enda scen / VD (Virtual Device)
- Globala som nås från alla scener / VD
Sedan finns en variant av variabel som vi kan kalla listor, eller vektorer - men det är lite svårare så jag tar det senare.

Om vi börjar med Lokala variabler, så definierar vi dom enklast såhär:

Code: Select all

local RadioKanal
Och en enkel tilldelning kan t.ex. ske såhär:

Code: Select all

RadioKanal = tostring(math.random(23,26))
Båda samtidigt:
Image

Vad sker ovan, jo variabel RadioKanal, som är local, existerar enbart i denna scen (för detta är från en scen), och innehåller en slumpvis vald RadioKanal mellan "23" och "26" - slumptalsfunktionen "math.random(23,26)" returnerar ett heltal mellan 23 och 26, men vi behöver det i textformat (sträng som det kallas), varför vi även använder "tostring()" - det som står innanför parentes kommer konverteras från numeriskt värde till text.

Man kan såklart undra varför man skall göra just detta, en RadioKanal mellan 23 och 26 - jo i mitt exempel, som är taget från min "Radio @Home" scen, så startas alltså radion i bakgrundsläge och det sker genom att man senare ropar på en VD där man trycker knapp 23 till knapp 26 (kommer till det någon gång), varpå min hemmabio startar upp och börjar spela den valda internet radio kanalen när man är hemma (och således stänger av allt när man går hemifrån, mycket trevlig feature) .

I vilket fall, detta är alltså en enkel Lokal variabel. Och den fungerar alltså bara i den scen eller VD man skriver koden i.

En Global variabel har sitt värde generellt hela tiden i HC2, och den måste hanteras lite annorlunda. Först så skapar man variabeln, och det sker under en av panelerna i HC2 (klicka på Variabelpanel - det rödmarkerade området):
Image

När man kommer in i denna panel, så kommer man få i praktiken TVÅ val - det beror lite på hur man avser att den globala variabeln skall fungera. Man får välja mellan en vanlig variabel som kan innehålla vilket värde som helst, eller en variabel med fördefinierade värden (se de två rödmarkerade områdena):
Image

Som ni ser har jag (för detta är en skärmdump från min egna HC2) bara EN vanlig global variabel, och ELVA fördefinierade globala variabler. Vi skall nu prova att skapa en variabel av varje typ, så vi får prova på hur det är och upplevs. Vi börjar med en vanlig global variabel, klicka på den övre rödmarkerade knappen "Lägg till":
Image

Vi skapar variabeln "Test" och spara den genom att trycka på knappen "Spara":
Image

Och som synes så har vi nu skapat en ny variabel:
Image

Det var enkelt. Och så är det även med en fördefinierad variabel, alltså en variabel som bara kan innehålla fasta i förväg bestämda värden. Vi börjar precis som ovan med att trycka "Lägg till" men nu den NEDRE knappen, och valet blir lite annorlunda:
Image

Vi kallar således variabel för "Test2", och som fasta värden väljer vi:
Image

Och trycker precis som förra gången "Spara"
Image

Varpå det ser ut såhär:
Image

Om man tittar lite mera noga så ser man, markerat med blått, två ytterligare symboler på samma rad som variabeln "Test":
Image

Den första symbolen, ser ut som en penna, är mycket riktigt den man klickar på för att ÄNDRA möjliga värden på varibeln. Den med kryss är samma sak som att RADERA variabeln. Samma sak gäller ovan för den första variabeln vi skapade, där finns dock bara ett kryss för RADERING, men å andra sidan har vi ett fritextfält där man kan mata in valfritt värde för variabel.

OBS OBS OBS OBS OBS

Om man manuellt ändrar värdet på en variabel under denna panel, så kommer scener som har dessa variabler som triggervärden INTE att köras (se LUA för nybörjare - Del 1) - att ändra en variabel här inne i variabelpanelen startar således INTE några scener - det sker bara när man ändrar en variabel i en scen eller VD. Och detta är mycket viktigt att komma ihåg!!!

OBS OBS OBS OBS OBS

Således nu när vi skapat våra globala variabler så kan vi börja nyttja dom. Och globala variabler, oberoende av vilken av dessa två varianter som vi har, behöver anropas på ett speciellt sätt för att refereras till. Dvs man kan inte bara skriva:

Code: Select all

Test = 1
Det kommer inte fungera, då kommer LUA och HC2 tro att du refererar till den LOKALA variabeln "Test" - och det är troligen inte syftet i detta exempel. Eftersom referens av ett värde i en global variabel hanteras genom en funktion som tillhandahålles av HC2, så finns det i praktiken TRE olika anrop man kan göra. Och enklast att se dessa är att skapa en scene (LUA kodning), och placera muspekaren uppe till vänster (där det står Variabellista, Variabler, och välj "Test" så ser du valbara metoder):
Image

Som synes har jag markerat "getGlobalValue()" och klickar jag på denna så kommer full syntax upp på vald variabel i scenens LUA fönster:
Image

OBServera att det kommit lite mera kod, och utan att gå för djupt så fungerar det lite såhär: Vi behöver hämta en global variabel, det krävs en funktion för att göra det, och den tillhandahåller fibaro åt oss (det är dom som implementerat denna variant av LUA): fibaro:getGlobalValue("Test") - alltså tillhandahållare:funktion(något att göra) - ja lite grovt förenklat alltså... Nej det är inte 100% korrekt, utan förenklat...

Men sådär lär det inte fungera speciellt bra ;-) Utan vi får nu hitta på något, som att t.ex. skapa en lokal variabel och flytta över värdet, och så skriver vi ut det med fibaro:debug funktionen (om ni undrar vad de två små punkterna gör på slutet av den koden så är det en "concatination" - dvs man slår ihop två det första med det andra och presenterar det som en enda massa):
Image

Kul - så nu har vi fått tag i värdet, men hur manipulerar vi värdena då? Ja lokala variabler räcker det med ett likamed tecken "=", det har vi sett tidigare, men om man skall TESTA ett värde så krävs DUBBLA likamed tecken "==", och om vi samtidigt passar på att skriva lite mera LUA kod så kan det se ut såhär:
Image

Alltså, först skapar vi den lokala variabeln "lokal_test", sedan hämtar vi värdet från den globala variabeln "Test", vi skriver ut båda värdena, och sedan tilldelar vi "lokal_test" värdet 2 (istf värdet noll som vi skapade den globala variabeln "Test" med som ursprungsvärde, dvs "default value" som det så vackert kallas), skriver ut detta för att verifiera att vi ändrat värdet, sedan testar vi om värdet av den lokala variabeln "lokal_test" verkligen är just två, och om det är det - och mycket skall till för att det inte skall vara det i denna hårdkodade variant av scen - så flyttar vi in värdet från den lokala variabeln "lokal_test" till den globala variabeln "Test". Och för att slutligen verifiera att vi verkligen ändrat värdet, så skriver vi ut värdet igen. Men vi kan också, såklart, verifera värdet genom att gå till variabelpanelen där vi skapade den globala variabeln:
Image

Som synes har den globala variabeln "Test" ändrat värdet.

Jag vill också göra er observanta på att funktionen för att tilldela en global variabel ett värde alltså syntaxmässigt skiljer sig åt från att hämta:

Code: Select all

fibaro:getGlobalValue("Test")
vs.

Code: Select all

fibaro:setGlobal("Test", lokal_test)
Om ni tittar noga så är det alltså "getGlobalValue" resp "setGlobal" - alltså INGET setGlobalValue (vilket man kan tro, och som faktiskt råkar finnas som funktion också, även om den verkar vara lite odokumentrad typ). Jag har själv lyckats förvirra mig bort i just detta, så om ni kan undvika samma misstag som jag gjort så är det great :-)

Här kommer således hela koden för denna experiment scen:

Code: Select all

--[[
%% properties
%% globals
--]]

--start here
local lokal_test
lokal_test = fibaro:getGlobalValue("Test")

fibaro:debug("Variabeln Test har värdet: "..fibaro:getGlobalValue("Test"))
fibaro:debug("Variabeln lokal_test har värdet: "..lokal_test)

lokal_test = 2

fibaro:debug("Variabeln lokal-test, efter manipulering, har värdet: "..lokal_test)

if ( lokal_test == 2 ) then
  fibaro:setGlobal("Test", lokal_test)
end

fibaro:debug("Variabeln Test har värdet: "..fibaro:getGlobalValue("Test"))
Om ni spara denna scen, och trycker på "Kör" så kommer ni få något i denna stil i fönstret under LUA koden:
[DEBUG] 19:42:22: Variabeln Test har värdet: 0
[DEBUG] 19:42:22: Variabeln lokal_test har värdet: 0
[DEBUG] 19:42:22: Variabeln lokal-test, efter manipulering, har värdet: 2
[DEBUG] 19:42:22: Variabeln Test har värdet: 2
Ja precis som planerat. Samma gäller för globala variabler med fasta värden, men i dessa fall kan man alltså bara tilldela den globala variabel dessa fasta värden. Och vi skapade en sådan global variabel "Test2" - vad kan man nyttja den till då? Jo i följande exempel så låter vi denna globala variabel visa på om det är ljust (Soligt) eller mörkt (natt eller moln ivägen för solen) och kan på så sätt få reda på via en global variabel hur status är (och ett av skälen till att välja denna lösning är att om man vill ändra känsligheten på brytgränsen för soligt/mörkt så är det bara EN plats att ändra på - men variabeln kan nyttjas på många platse, kodar man varibeln fast med ett fast värde att testa mot så måste man ändra på flera platser eller skapa en variabel som innehåller brytgränsen osv):

Code: Select all

--[[
%% autostart
%% properties
54 value
104 value
%% globals
--]]

-- Ta bort scener med samma ID som denna, så att bara EN av denna scene körs.
if (fibaro:countScenes() > 1) then fibaro:abort() end

-- Ta reda på om scenen startats genom trigger eller genom värdeändring på
-- någon av de IDn som angivits ovan under %% properties. Detta får man reda
-- på genom att läsa av funktionen fibaro:getSourceTrigger
local startSource = fibaro:getSourceTrigger()

if (
-- Om scenen startats genom ändrat värde, kolla om värdet för ljussensorerna
-- är högre än 280 för BÅDA sensorerna för att skapa viss tröghet i scenen.    
 ( tonumber(fibaro:getValue(54, "value")) > 280 and
   tonumber(fibaro:getValue(104, "value")) > 280 and  
-- Och förutsätt att det tidigare värdet för den globala variabeln "Test2"
-- var "Mörkt" så att vi inte skapar onödig körning av koden
   fibaro:getGlobalValue("Test2") == "Mörkt" )
-- Någon har startat denna scen genom att köra manuellt
   or startSource["type"] == "other"
)
then
-- Tilldela den globala variabeln "Test2" värdet "Soligt" eftersom det är
-- tillräckligt ljust ute för att kalla det soligt (obs en ljus mulen dag
-- kommer att trigga också, då det indirekta ljuset som faller på ljussensorn
-- är mer än tillräckligt för att trigga värdeförändring)
  fibaro:setGlobal("Test2", "Soligt")
end

if (
-- Om scenen startats genom ändrat värde, kolla om värdet för ljussensorerna
-- är lägre än 230 för NÅGON av sensorerna.    
 ( ( tonumber(fibaro:getValue(54, "value")) < 230 or
     tonumber(fibaro:getValue(104, "value")) < 230 ) and
-- Och förutsätt att det tidigare värdet för den globala variabeln "Test2"
-- var "Soligt" så att vi inte skapar onödig körning av koden
   fibaro:getGlobalValue("Test2") == "Soligt" )
)
then
-- Tilldela den globala variabeln "Test2" värdet "Soligt" eftersom det är
-- tillräckligt ljust ute för att kalla det soligt (obs en ljus mulen dag
-- kommer att trigga också, då det indirekta ljuset som faller på ljussensorn
-- är mer än tillräckligt för att trigga värdeförändring)
   fibaro:setGlobal("Test2", "Mörkt")
end

-- Debug area - här skriver vi till slut ut värdena så vi vet vad som hände:
fibaro:debug("Ljussensor 54 har värdet: "..fibaro:getValue(54, "value"))
fibaro:debug("Ljussensor 104 har värdet: "..fibaro:getValue(104, "value"))
fibaro:debug("Den globala variabel Test2 har värdet: "..fibaro:getGlobalValue("Test2"))
Som ni ser är detta en mer eller mindre komplett scen - jag har gått igenom det mesta tidigare, så det känns lite onödigt att täcka allt - jag har kommenterat koden rätt friskt istället. MEN en sak har tillkommit, och det är i första början där jag nu inkluderar %% autostart:
Image

Som ni ser ovan så har vi alltså:
%% autstart
%% propertis
%% globals

Autostart innebär att scenen körs så fort vi sparar den, eller HC2 startas om - autostart alltså. Mycket praktiskt, men man måste såklart vara försiktig, om man inte är det, så kan man råka starta andra scener som t.ex. triggas av värden på gloabal variabler som används som triggers.
Properties innebär att scenen körs så fort som värdet för den eller de enheter som listas upp. I exemplet ovan är det alltså två av mina ljussensorer som kan trigga scenen, deviceID = 54 eller deviceID = 104.
Globals innebär att scenen körs så fort som värdet på den globala variabeln förändras - och det är här man alltså från en scen som sätter värdet på en global variabel kan starta en kedjereaktion så att andra scener startas såfort som värdet ändrats. Mycket praktiskt, men som sagt, kedjereaktioner kan ibland - speciellt om man gör komplexa LUA scener - skapa problem som man kanske inte alltid tänkt på. Rätt använt är det grymt häftigt, fel gjort kan man nog få en och annan utmaning...

Om vi tittar igen på samma bild igen:
Image
Så ser vi att jag även inkluderade en liten rad som dödar samma scen om den redan körs, så att man bara har EN variant av scenen som är aktiv och körs - extra viktigt om man har loopar som är oändliga eller liknande, eller "sleep" funktioner som hänger sig och dyligt...
Vidare så ser vi en annan sak som ibland skymtas:

Code: Select all

-- Ta reda på om scenen startats genom trigger eller genom värdeändring på
-- någon av de IDn som angivits ovan under %% properties. Detta får man reda
-- på genom att läsa av funktionen fibaro:getSourceTrigger
local startSource = fibaro:getSourceTrigger()
Det här har att göra med HUR en scen startas. OM vi tittar på detta med %% autostart igen - så är det alltså så att skälet till att scenen startas såfort man trycker spara, är för att "%% autostart" är med i starten av LUA koden. MEN för att få till en körning så måste man ibland fånga ljust att man kör koden så att säga, och iofs är det väl överkurs delvis i detta exempel, men likväl viktigt att försöka komma ihåg. Detta görs genom att man helt enkelt tilldelar en variabel värdet från funktionen "fibaro:getSourceTrigger()" - och om värdet är "other" (se if testet längre ned i koden, som jag skrev ut ovan) så vet man att skälet till att scenen körs är för att någon t.ex. tryckt "kör" eller liknande - dvs skälet är inte att värdet på t.ex. sensorer eller globala variabler ändrats utan för att man manuellt helt enkelt triggat körningen av scenen.

I vilket fall, såhär blir utskriften av debug raderna på slutet om allt fungerar som det skall:
[DEBUG] 20:21:51: Ljussensor 54 har värdet: 2
[DEBUG] 20:21:51: Ljussensor 104 har värdet: 22
[DEBUG] 20:21:51: Den globala variabel Test2 har värdet: Mörkt
[DEBUG] 20:29:44: Ljussensor 54 har värdet: 10
[DEBUG] 20:29:44: Ljussensor 104 har värdet: 22
[DEBUG] 20:29:44: Den globala variabel Test2 har värdet: Mörkt
Och tittar vi på variabelpanelen så har mycket riktigt värdet ändrats (vi skapade variabeln "Test2" med värdet "Soligt" från början):
Image

Så vad kan man göra med den globala variabeln? Jo om man tittar på vad jag skrev ovan så kan vi starta en scen med hjälp av just värdeförändring på en global variabel, t.ex. något i stil med detta som tänder resp släcker våra fönsterlampor när solen går ned/i moln eller kommer fram/stiger upp på morgonen:

Code: Select all

--[[
%% autostart
%% properties
%% globals
Test2
--]]

--kill any extra instances of the same scene
if (fibaro:countScenes() > 1) then fibaro:abort() end

-- Mörkt, tänd ljusen i fönstren
if ( fibaro:getGlobalValue("Test2") == "Mörkt" )
then
-- Julbelysning
    fibaro:call(146, "turnOn")
-- Skapa en fördröjning mellan 3.5 sekunder till 4.5 sekunder
      fibaro:sleep(math.random(3500,4500))
-- Vanliga fönsterbelysningen
    fibaro:call(48, "turnOn")
end

-- Ljust, släck ljusen i fönstren
if ( fibaro:getGlobalValue("Test2") == "Soligt" )
then
-- Vanliga fönsterbelysningen
    fibaro:call(48, "turnOff")
-- Skapa en fördröjning mellan 3.5 sekunder till 4.5 sekunder
       fibaro:sleep(math.random(3500,4500))
-- Julbelysning
    fibaro:call(146, "turnOff")
end
Således i denna scen kommer vi att tända eller släcka lampor vartefter ljussensorerna indikerar Mörkt eller Soligt. Mycket praktiskt, och själv gillar jag denna variant över solens upp/nedgång - är det mörkt ute (åskväder någon?) så vill iallafall jag ha det tänt i fönstren när jag kommer hem! Så mycket mera välkomnande!

Jag hade ursprungligen tänkt ta detta med variabel listor (vektorer) men jag spårade ur lite ovan så jag tar det i del 2.2 ?
Testar Home Assistant på Raspberry Pi4B - nice :mrgreen:
SirMaggot
Medlem
Posts: 441
Joined: 18 Aug 2013, 10:36
10

Tack för att du tar dig tid Bamsefar, alltid trevligt att läsa.
Post Reply