Skip to main content

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ä.

Actiweb laitteen /opt/slc/prg/run/ hakemisto 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