Sovellusohjelmat
Sovellusohjelma ja tehtävä ovat tässä dokumentissa oikeastaan synonyymejä. Käyttäjän kannalta sovellusohjelma sisältää lua koodin lisäksi myös mm. käyttöliittymän grafiikkasivut, joita ei käsitellä tässä dokumentissa.
Tehtävien eli task:ien luominen
Mikäli käytetään vakio versiota käynnistys skriptistä, on tehtävien luominen järjestelmään hyvin helppoa, sillä käynnistys skripti etsii kaikki ”.lua” -päätteiset tiedostot /opt/slc/prg/run/ hakemistosta, ja käynnistää jokaisen niistä omana tehtävänään. Mikäli jossakin yhteydessä on tärkeätä hallita näiden ohjelmien käynnistysjärjestystä, se voidaan tehdä nimeämällä ohjelmatiedostot sopivasti. Järjestelmä nimittäin ensiksi hakee kaikkien hakemistossa sijaitsevien tiedostojen nimet, järjestää ne aakkosjärjestykseen – tai oikeastaan numerojärjestykseen ASCII merkistötaulukon mukaisesti pienimmästä suurimpaa. Näin ollen antamalla tiedostonimille alkuliitteet vaikkapa 01 .. 99 saadaan ohjelmien käynnistysjärjestys halutuksi. Esimerkin tapauksessa tiedosto joka alkaa 01 ladataan ja suoritetaan ensimmäisenä, ja tiedosto joka alkaa 99 ladataan viimeisenä.
Kuvassa on näkyvissä Actiweb laitteen /opt/slc/prg/run/ hakemistossa olevat automaattisesti käynnistyvät autorun ohjelmat.
Kuten sanottua, uusia task:eja eli tehtäviä on mahdollista luodaan lisäämällä uusi ”.lua”-päätteinen tiedosto /opt/slc/prg/run/-hakemistoon. Kun tällainen automaattisesti käynnistettäv ohjelma ladataan, ja järjestelmä luo siitä taskin, annetaan sille aluksi seuraavat oletusasetukset:
• Suoritusväli 1000 ms
• Ajoitustapa cyclic (eli suorituskertojen välillä odotetaan 1000 ms)
• Nimi on tiedoston nimi
Näitä oletusarvoja on mahdollista muuttaa Slc kirjaston kutsuilla Slc.setTaskName (), Slc.setSchedule (), Slc.setTiming (). Tätä hakemistoa kutsutaan lyhyesti autorun hakemistoksi.
Latausprosessi toimii siten, että ensiksi kääntäjä lataa ja kääntää autorun-hakemistossa olevan lähdekooditiedoston. Mikäli kyseinen tiedosto viittaa muihintiedostoihin esimerkiksi ”require” käskyllä, ne ladataan ja käännetään samalla hetkellä kun kääntäjä törmää näihin viittauksiin. Alkuperäisen tiedoston kääntäminen jatkuu kun viitattu tiedosto on käsitelty.
Kun tiedosto on käännetty tavukoodiksi laitteen muistiin, se suoritetaan kokonaisuudessaan yhden kerran. Tämän jälkeen käynnistyy varsineinen ajoitettu suoritussykli, jossa actiweb ohjelmisto kutsuu asetetulla suoritusvälillä ohjelman niin sanottua pääfunktiota. Autorun hakemistosta käynnistetyillä ohjelmilla se on aina nimeltään ”main”. Tämä nimitys mukailee monia ohjelmointikieliä kuten C, C++, Java tai C#.
Tämä suoritustapa tarkoittaa sitä, että pääfunktion ulkopuolinen ohjelmakoodi suoritetaan yhden kerran käännösvaiheen jälkeen, eli siellä voidaan hoitaa esimerkiksi erilaisia alustukseen liittyviä toimenpiteitä, kuten määrittää asetuksia tai globaaleita muuttujia.
Alla on esimerkki hyvin yksinkertaisesta Lua kielisestä sovellusohjelmasta, joka antaa hälytyksen mikäli mittaus ylittää raja-arvon. Ohjelma olettaa että pistetietokannassa on olemassa ”TE20” ja ”TE20_HI_AL” nimiset tietokantapisteet.
Esimerkki 1:
/opt/slc/prg/run/example1.lua
Slc.setSchedule (”periodic”)
Slc.setTiming (1000)
Slc.setTaskName (”simpleAlarmTask”)
function main()
local m = Data.getReal (”TE20.pv”)
local hilimit = Data.getReal (”TE20.hi”)
local hyst = 0.5
if m > hilimit then
Data.set (”TE20_HI_AL.pv”, 1)
elseif m < (hilimit - hyst) then
Data.set (”TE20_HI_AL.pv”, 0)
end
end
Yllä oleva ohjelma käynnistyy siis automaattisesti omaksi prosessikseen kun tiedosto on luotu, ja näkyy siten myös käyttöliittymässä system→setting -sivun alaosan tehtävälistassa. Tuossa listassa näytetään Slc.setTaskName () kutsulla asetettu nimi.
On hyvä ymmärtää kuinka lua tiedostostot ladataan ja suoritetaan. Koska lua on käyttäjän kannalta tulkattu kieli, näyttää tilanne siltä että sovellusohjelmat (ja muutkin komennot) suoritetaan suoraan tekstitiedostosta tai komentiriviltä, joka on täysin poikkeavata esimerkiksi C-kieleen verrattuna. Todellisuudessa lua-koodi käännetään samalla hetkellä kun käyttäjä pyytää koneelta että lähdekooditiedosto ajetaan. Tiedosto käydään lävitse, ja mikäli siinä ei ole syntaksi-virheitä, siitä tehdään RAM muistiin niin sanottu tavukoodi ”möhkäle” - eli chunk. Tavallisesti tätä tavukoodikäännöstä ei kirjoiteta levylle. Tämän jälkeen perinteisen lua (tai python) ympäristön tapauksessa tulkki ryhtyy sitten suorittamaan tätä tavukoodia, ja osa virheistä paljastuu vasta tässä vaiheessa. Slc engine käyttää kuitenkin lua ohjelmien suorittamiseen niin sanottua JIT kääntäjää, jolloin tavukoodi käännetäänkin suoraan natiiviksi konekieliseksi ohjelmakoodiksi, jonka prosessori sitten suorittaa. Teoriassa ohjelman kääntäminen kestää tässä tapauksessa hieman kauemmin, mutta toisaalta, ohjelmakoodin suorittaminen on hyvin nopeata.
Funktiot ja globaalit muuttujat
Alla olevassa esimerkissä näytetään muutama hieman edistyneempi rakenne.
Esimerkki 2:
/opt/slc/prg/run/example2.lua
Slc.setSchedule (”periodic”)
Slc.setTiming (1000)
Slc.setTaskName (”mySecondTask”)
counter = 0
function inc(i)
return i+1
end
function main()
counter = inc(counter)
Slc.echo (”Round ”.. counter )
end
Ensimmäinen tärkeä seikka on counter muuttuja. Sen edessä ei ole local sanaa, jolloin se määrittyy globaaliksi muuttujaksi, ja on esittelyn jälkeen käytössä kaikkialla ohjelmassa. Globaaleilla muuttujilla on lisäksi tärkeä piirre, että ne säilyttävät arvonsa suorituskertojen välillä, eli niin kauan kuin Lua suoritusympäristöä ei alusteta uudelleen. Tämä tapahtuu pääasiassa silloin, kun koko Actiweb ohjelma uudelleenkäynnistetään – eli esimerkiksi painetaan restart -painiketta käyttöliittymässä, tai koko laitteisto uudelleenkäynnistyy. Joitakin laskureita ja viiveitä voi siis aivan hyvin tehdä jopa ylläolevalla tavalla.
Toinen huomionarvoinen piirre on funktio inc() – tämä liittyy itse lua-kieleen. Ohjelmien ylläpidon ja uudelleenkäytettävyyden kannalta olisi hyvä tapa, että samaa ohjelmakoodia kirjoiteta kuin yhden kerran. Tätä varten useimmissa ohjelmointikielissä on jokin tapa luoda aliohjelmia, jotka tekevät tietyn. Lua kielessä ne kuvataan function-avainsanan avulla, ja ohjelmointikielestä ja tilanteesta riippuen niitä voidaan kutsua fuktioiksi, rutiineiksi tai proseduureiksi. Kun ohjelman toistuvista osista tehtään funktio, eräs suurimmista hyödyistä on, että siinä olevaa virhettä ei tarvitse korjata kuin yhteen paikkaan. Toisaalta, kun rutiini nimetään järkevästi, se myös yksinkertaistaa sitä ohjelman kohtaa, josta rutiinia kutsutaan. Se taas on ohjelman monimutkaisuuden hallinnan kannalta korvaamatonta, ja myös eräs proseduraalisen ohjelmointitavan keskeisistä ajatuksista.
Oliot
Ohjelman ymmärrettävyyden kannalta on usein hyödyksi, kun sekä data, että toiminnallisuus (eli rutiinit) saadaan liimattua yhteen, jolloin tuloksena on olio.
Vaikka tämän oppaan tarkoituksena ei ole opettaa ohjelmointia yleisesti, eikä lua kieltä erityisesti, vain esittää mitä lua kielisten ohjelmien kirjoittaminen Slc engine ympäristöön vaatii, käymme seuraavassa lävitse olioiden (eng. objects) luomisen perusteet.
Lua kielessä oliot perustuvat prototyyppeihin ja constructor funktioihin (kuten myös esimerkiksi ECMA script -kielessä). C++ ja Java kielissä taas oliot luodaan n.s. luokkien pohjalta, mikä on hieman byrokraattisempi lähestymistapa, ja sopii paremmin vahvasti tyypitettyihin kieliin.
Esimerkki 2:
/opt/slc/prg/run/example3.lua
Slc.setSchedule (”periodic”)
Slc.setTiming (1000)
Slc.setTaskName (”mySecondTask”)
-- Lets create object prototype cSimplePump
cSimplePump = {}
cSimplePump.new = function (s, idDI, idDO, idAL)
local c = {}
c.idDI = (idDI or ””)
c.idDO = (idDO or ””)
c.idAL = (idAL or ””)
local c.state = 0 -- off by default
c.setState = function (s, nState)
if type(nState) ~= ”number” then
return false
elseif nState<0 then
return false
elseif nState>1 then
return false
else
s.state = nState
return true
end
end
c.runLogic = function (s)
-- Control conflict alarm
local DI = Data.getReal (s.idDI)
local DO = Data.getReal (s.idDO)
if DI ~= DO then
Data.set ( s.idAL, 1)
else
Data.set ( s.idAL, 0)
end
-- Control logic
if s.state == 1 then
Data.set ( s.idDO, 1)
else
Data.set ( s.idDO, 0)
end
end
return c
end
-- Create pumps
PU01 = cSimplePump:new (”PU01_DI.pv”, ”PU01_DO.pv”, ”PU01_AL.pv”)
PU02 = cSimplePump:new (”PU02_DI.pv”, ”PU02_DO.pv”, ”PU02_AL.pv”)
PU03 = cSimplePump:new (”PU03_DI.pv”, ”PU03_DO.pv”, ”PU03_AL.pv”)
function main()
-- Handle all pumps
if Data.getReal (”PU_ENABLED.pv”) > 0 then
PU01:setState (1)
PU02:setState (1)
PU03:setState (1)
else
PU01:setState (0)
PU02:setState (0)
PU03:setState (0)
end
PU01:runLogic ()
PU02:runLogic ()
PU03:runLogic ()
end