Aller au contenu

Questions de débutant en Quick Apps sur HC3


Messages recommandés

Posté(e)

Maybe I was not enough clear :P

Yes I put your proposal intervalRunner (10, function () checkVariables (self) end) in onInit() instead of intervalRunner (10, checkVariables(self)). And it works very well to be able to call in my code QA variable.

 

But putting your proposal intervalRunner (10, function () checkVariables (self) end) in onInit() instead of intervalRunner (10, checkVariables(self)), make the ":EXIT" not running anymore.

When I put return ":EXIT" at the end of my code, intervalRunner ignore and continue to run and never stop.

It seems like if fun()==":EXIT" or ref[1]==nil then return end doens't find anymore the return ":EXIT" coming from my code when I put intervalRunner (10, function () checkVariables (self) end) in onInit() instead of intervalRunner (10, checkVariables(self))

Posté(e)
il y a 19 minutes, Fredmas a dit :

Maybe I was not enough clear : P

Yes I put your proposal intervalRunner (10, function () checkVariables (self) end) in onInit () instead of intervalRunner (10, checkVariables (self)) . And it works very well to be able to call in my code QA variable.

 

But putting your proposal intervalRunner (10, function () checkVariables (self) end) in onInit () instead of intervalRunner (10, checkVariables (self)) , make the ": EXIT" not running anymore.

When I put return ": EXIT" at the end of my code, intervalRunner ignore and continue to run and never stop.

It seems like if fun () == ": EXIT" or ref [1] == nil then return end doens't find anymore the return ": EXIT" coming from my code when I put intervalRunner (10, function () checkVariables (self) end) in onInit () instead of intervalRunner (10, checkVariables (self))

 

Sorry, my bad.

Solution is

intervalRunner (10, function () return checkVariables (self) end)

Posté(e)

We could do intervalRunner a little better

function intervalRunner(seconds,fun,...)
  local args,nxt,ref={...},nil,{}
  local function loop()
    if fun(table.unpack(args))==':EXIT' or ref[1]==nil then
      return
    end
    nxt=nxt+seconds
    ref[1] = setTimeout(loop,1000*(nxt-os.time()))
  end
  local t = ((os.time()-3600) // seconds) * seconds
  nxt=t+seconds+3600
  ref[1] = setTimeout(loop,1000*(nxt-os.time()-(seconds > 3600 and 3600 or 0)))
  return ref
end

Then we can do

intervalRunner (10, checkVariables, self)

Posté(e)
il y a 16 minutes, jang a dit :

 

Sorry, my bad.

Solution is

intervalRunner (10, function () return checkVariables (self) end)

Perfect. I didn't think just to put "return" in the line in onInit :unsure: Not so easy to learn from zero :D

I understood and it works.

Posté(e)
il y a 12 minutes, jang a dit :

We could do intervalRunner a little better


function intervalRunner(seconds,fun,...)
  local args,nxt,ref={...},nil,{}
  local function loop()
    if fun(table.unpack(args))==':EXIT' or ref[1]==nil then
      return
    end
    nxt=nxt+seconds
    ref[1] = setTimeout(loop,1000*(nxt-os.time()))
  end
  local t = ((os.time()-3600) // seconds) * seconds
  nxt=t+seconds+3600
  ref[1] = setTimeout(loop,1000*(nxt-os.time()-(seconds > 3600 and 3600 or 0)))
  return ref
end

Then we can do

intervalRunner (10, checkVariables, self)

:blink: sounds good but new for me, even if I already read weeks ago something about "unpack" here.

But I will go step by step, as now I understood the way to code the previous version :D

And I keep it in my notes for a next step :2:

Posté(e)

Thanks a lot @jang  and @Lazer  I understood and learnt a lot about starting with QA and about the way to loop :)

Everything is now running properly in the loop, and I continue to code my main function in this QA instead of scenes. ;)

 

 

 

The only thing I didn't succeed to put in place is the function stopIntervalRunner.

Le 03/07/2021 à 11:19, jang a dit :

You can also stop it with calling stopIntervalRunner(ref) with the ref that intervalRunner returns.

I didn't succeed to understand where and how to call it.

I tryed to write this function like that:

function stopIntervalRunner(ref)
  if ref[1] then
    clearTimeout(ref[1])
    ref[1]=nil
  end
end

Then I tryed to call it from my main code (or somewhere else) for a test, but I didn't find how and what to put for calling stopIntevalRunner(ref).

I understood that you said: "with the ref that intervalRunner returns". But I probably failed to catch the ref returned by intervalRunner()

 

 

 

But anyway, at the end I put in place another solution with a simple button like below, in case I need to stop intervalRunner, and it works good:

function QuickApp:button2()
  if self:getVariable("QAOnOff") == "on" then
    self:setVariable("QAOnOff", "off")
    self:updateView("button2", "text", "QA is OFF. Pusch to switch on")
  else
    self:setVariable("QAOnOff", "on")
    self:updateView("button2", "text", "QA is ON. Push to switch off")
    intervalRunner(60, function() return mainCode(self) end)
  end
end

It is not perfect because I wrote here again the same line intervalRunner(60, function() return mainCode(self) end), duplicated from onInit(). Not good when we want to change the value and for the maintenance, but I will probably never change it, or not so often :P

And in the main code I put at the beginning:

function mainCode(self)
  if self:getVariable("QAOnOff") == "off" then
  print("QA is off")
  return ":EXIT"
  end
  
  blablabla
  
end

 

Have a good day, Bonne journée,

Fred

 

 

Posté(e)
function intervalRunner(seconds,fun,...)
  local args,nxt,ref={...},nil,{}
  local function loop()
    if fun(table.unpack(args))==':EXIT' or ref[1]==nil then
      return
    end
    nxt=nxt+seconds
    ref[1] = setTimeout(loop,1000*(nxt-os.time()))
  end
  local t = ((os.time()-3600) // seconds) * seconds
  nxt=t+seconds+3600
  ref[1] = setTimeout(loop,1000*(nxt-os.time()-(seconds > 3600 and 3600 or 0)))
  return ref
end

function stopIntervalRunner(ref)
  if ref[1] then
    clearTimeout(ref[1])
    ref[1]=nil
  end
end

function QuickApp:onInit()
  local ref,i=nil,0
  ref = intervalRunner(2,function()
      i=i+1
      print(i)
      if i == 5 then stopIntervalRunner(ref) end
    end)
end

 

Posté(e)

You are too strong and fast for me. Thanks :D

I didn't think to put ref in onInit() :unsure:

 

Updating your proposal to my QA, like below it seems to work simply for me :P

--to initialize the QA
function QuickApp:onInit()
  self:debug("onInit")
  ref = intervalRunner(2, function() return mainCode(self) end)
end

--to stop the QA
function QuickApp:button3()
  stopIntervalRunner(ref)
end
function stopIntervalRunner(ref)
  if ref[1] then
    clearTimeout(ref[1])
    ref[1]=nil
  end
end

--to loop the main code of this QA
function intervalRunner(seconds,fun)
...
end

--the main code to run from this QA
function mainCode(self)
  print("Hereafter the main code to run")
end

 

Posté(e)

You could make the button toggle intervalRunner

--to initialize the QA
function QuickApp:onInit()
  self:debug("onInit")
  ref={} -- Start running
  self:button3()
end

--to toggle running the QA
function QuickApp:button3()
  if ref[1] then -- Running
     stopIntervalRunner(ref)
     self:updateView("<button3 id>","text","Halted")
  else
     ref = intervalRunner(2, function() return mainCode(self) end)
     self:updateView("<button3 id>","text","Running")
  end
end

"<button3 id>" should be the id of the button.

  • Like 1
Posté(e)

Yes I like it :D

It was something like that about what I was thinking drinking my afternoon coffee :P

But I am not sure that I would think so fast:

1. to move the call of intervalRunner() from onInit() to a QuickApp object like this button3(), called itself by onInit()

2. to define ref as a table and not a value in onInit()

 

But reading your proposal I understand the good idea and how it works :lol:

Much more simple and easier to maintain than my previous way to do with button, variable and return ":EXIT"

Posté(e) (modifié)

#3 Question 3 : définir correctement une fonction comme membre du QA

 

J'ai compris que pour qu'une fonction prenne des commandes self par exemple, et donc utilise les méthodes du QA, cette fonction doit être membre du QuickApp (ajoutée au QuickApp class).

Cependant à la lecture des sujets et des manuels Fibaro, on constate plusieurs types d'écriture et manières de déclarer une fonction pour qu'elle soit membre du QA, en tout cas j'en ai retenu au moins 2.

 

Après avoir réalisé quelques essais simples, les 2 manières décrites ci-dessous fonctionnent, donc j'imagine que la subtilité se situe peut-être davantage au niveau de certaines utilisations des paramètres et des arguments.

Afin d'éclaircir les codeurs découvrant le monde des QA, quelqu'un saurait-il expliquer qu'elles sont les différences entre les 2 écritures suivantes ;)

 

Ecriture #1

function onInit()
  self:debug("onInit")
  test(self)
 end

function test(self)
  self:debug("hello")
end

 

Ecriture #2

function onInit()
  self:debug("onInit")
  self:test()
end

function QuickApp:test()
  self:debug("hello")
end

 

 

 

p.s. : petit hors sujet, pourquoi dans la plupart des QA que nous trouvons, le onInit est très souvent rédigé à la fin du code ? J'ai plutôt tendance à l'écrire au début, est-ce une erreur ? :huh:

Modifié par Fredmas
Posté(e)

Tes 2 écritures sont totalement différentes.


Dans la 1ère, la fonction test() n'est pas membre de la classe QuickApp. Elle est globale, c'est à dire qu'elle est accessible dans tous les fichiers LUA du QA. Mais vu qu'elle n'est pas membre de QuickApp, alors elle n'est pas accessible depuis l'extérieur (depuis un autre QA ou scène avec fibaro.call() ou via l'API HTTP)

 

Dans la 2nde, la fonction test() est bien membre de la classe QuickApp.

 

A titre de "best practices", je dirais que les fonctions qui doivent être publiées, c'est à dire accessibles depuis les autres QA, doivent être membre de QuickApp, tandis que les autres fonctions doivent être locales (donc avec le passage de self en paramètres si nécessaire).

 

 

Pour le QuickApp:onInit(), tu le mets où tu veux, c'est selon les préférences.

Souvent en codage, on retrouver les 1ère fonctions en bas du code, parce que lorsqu'elle sont exécutées, les autres fonctions ont déjà été définies préalablement, donc elles sont connues au moment de l'exécution.

Exemple :

function QuickApp:onInit()
	hello() -- Erreur : la fonction hello() n'a pas encore été définie
end

local function hello()
	print("world")
end

Il y a 2 façons de contourner le problème :

  • Mettre onInit() à la fin du code :
local function hello()
	print("world")
end

function QuickApp:onInit()
	hello() -- OK
end
  • déclarer la fonction au début du code, et la définir plus loin :
local hello

function QuickApp:onInit()
	hello() -- OK
end

hello = function()
	print("world")
end

 

  • Like 1
Posté(e)

En lua la seconde forme est inutile, on peut déclarer dans l'ordre que l'on veut. La notion historique de FORWARD du Pascal n'est pas nécessaire.

Posté(e)
il y a 21 minutes, Lazer a dit :

Tes 2 écritures sont totalement différentes.


Dans la 1ère, la fonction test() n'est pas membre de la classe QuickApp. Elle est globale, c'est à dire qu'elle est accessible dans tous les fichiers LUA du QA. Mais vu qu'elle n'est pas membre de QuickApp, alors elle n'est pas accessible depuis l'extérieur (depuis un autre QA ou scène avec fibaro.call() ou via l'API HTTP)

 

Dans la 2nde, la fonction test() est bien membre de la classe QuickApp.

 

A titre de "best practices", je dirais que les fonctions qui doivent être publiées, c'est à dire accessibles depuis les autres QA, doivent être membre de QuickApp, tandis que les autres fonctions doivent être locales (donc avec le passage de self en paramètres).

 

Merci @Lazer ton explication est claire, et je comprends mieux les 2 écritures et leur utilité. Je vais relire mes codes en cours :D

Du coup, pour vérifier si j'ai bien compris, j'ai pour habitude (et déformation professionnelle) de reformuler avec mes mots, et ça donne :

- la première écriture est locale au QA concerné (tu as écrit globale dans ta phrase alors j'ai le doute), donc accessible uniquement dans le QA concerné et tous ses fichiers, mais pas en dehors de ce même QA.

- la deuxième écriture est globale, donc utilise de la mémoire, mais la fonction est accessible en dehors du QA concerné si on l'appelle.

Ai-je bon ?

 

D'ailleurs, j'étais en train de réfléchir à justement écrire dans main tout ce qui concerne onInit, boutons, labels, boucles, etc., et créer un fichier pour ce que j'appelle le code fonctionnel, celui qui fait le boulot attendu :P

Et ceci uniquement à fin de fluidifier la lecture du QA. Bonne ou mauvaise idée ? :unsure:

Posté(e)

Pour le onInit et sa position dans le code, merci pour les réponses.

J'ai pris pour habitude de faire comme la deuxième méthode décrite par @Lazer, mais je comprends complètement l'intérêt de :

il y a 32 minutes, Lazer a dit :

Souvent en codage, on retrouver les 1ère fonctions en bas du code, parce que lorsqu'elle sont exécutées, les autres fonctions ont déjà été définies préalablement, donc elles sont connues au moment de l'exécution. 

 

 

Mais du coup, tout en écoutant attentivement, je vous laisse débattre au coin du comptoir de l'historique du FORWARD  :D

Pas à propos de Pascal (@mprinfo) mais bien des origines du Pascal. Bon ok je sors... <_<

 

Posté(e)

Je comprends ton besoin de reformuler, mais je t'invite à oublier l'utilisation que tu fais des termes "local" et "global", car ils prêtent à confusion car ce sont des termes qui existent en LUA.

Bref, tu as parfaitement compris la portée de la fonction hello() au sein du QA ou en dehors, mais ta formulation des fausse ;)

 

 

 

Posté(e) (modifié)

Reprenons :

-- Cette fonction est membre de la classe QuickApp. Elle est donc automatiquement publiée et accessible depuis l extérieur du QA
function QuickApp:test()
	self:debug("hello")
end

-- Cette fonction est "globale" (par défaut), c'est à dire accessible dans tous les fichiers du QA :
function test(self)
	self:debug("hello")
end

-- Cette fonction est locale (car spécifié) donc accessible uniquement dans le fichier en cours (ou bloc de code en cours si la fonction a été définie à l'intérieur d'une autre fonction/boucle/etc) :
local function test(self)
	self:debug("hello")
end

 

J'ajoute que l'utilisation des variables globales (et une fonction est en quelque sorte une variable au sens LUA) est à éviter autant que possible, car elles pourraient entrer en collision avec d'autres variables nommées à l'identique dans d'autres parties du code.

Une bonne pratique est de limiter la portée des variables à leur strict nécessaire, et à les passer en paramètres des fonctions quand nécessaire.

Cela devient de plus en plus important à mesure que notre code grossit, et qu'on réutilise des bouts de codes dans d'autres développements.

 

Un autre impact a lieu également sur les performances, car les variables globales elles sont rangées dans une super table globale nommée _G que l'interpréteur LUA doit parcourir à chaque fois qu'on fait appelle à ladite variable (ou fonction). C'est par conséquent plus lent que l'utilisation d'une variable (fonction) locale.

Pour une variable qui est appelée une fois par minute, ça ne change rien. Pour une boucle qui le ferait plusieurs fois par secondes, la différence devient sensible.

 

Rappel :

 

Modifié par Lazer
Posté(e)

QuickApp:onInit() is run after the whole file is loaded so it can be anywhere.

 

Fibaro does something like...

loadFile("QA file")      -- Everything in the file is run - all functions and globals are defined (also locals on "top level").
local self = QuickApp()  -- Now all methods are defined, create an instance
if self.onInit() then self:onInit() end -- If the user declared an :onInit call it
quickApp = qa            -- Set the globaö 'quickApp' to the current QuickApp self.

So :onInit() is called when everything is defined.

Also note that Fibaro sets the global 'quickApp' to self after :onInit() is done.  This means that we can use quickApp instead of passing around self to local functions if we are careful. It may not work if we deal with QuickAppChild instances as we then have many instances of "QuickApp" that we pass around...

 

And yes, we need to "forward" declare local variables sometimes.

This doesn't work

local function bar(x) if x > 0 then foo(x-1) else print('done')  end 
local function foo(x) bar(x-1) end
foo(5)

This work

local foo
local function bar(x) if x > 0 then foo(x-1) else print('done')  end 
function foo(x) bar(x-1) end
foo(5)

The reason is that when bar is defined/compiled, 'foo' is assumed to be a global variable as it has not been mentioned/seen earlier.

In the second example 'foo' is recognized as a local variable (even if it at the moment doesn't have a value)

 

  • Like 1
Posté(e) (modifié)
il y a une heure, Lazer a dit :

Je comprends ton besoin de reformuler, mais je t'invite à oublier l'utilisation que tu fais des termes "local" et "global", car ils prêtent à confusion car ce sont des termes qui existent en LUA.

Bref, tu as parfaitement compris la portée de la fonction hello() au sein du QA ou en dehors, mais ta formulation des fausse ;)

Le fait que je reformule n'est pas si mauvais, dans le sens où tu as certainement mis le doigt sur une belle incompréhension de ma part, puisqu'en écrivant ces mots, j'avais bien en tête globale et locale au sens variables LUA dans les scènes... :ph34r:

Du coup je vais te relire et me relire pour vérifier ma compréhension...

 

il y a une heure, Lazer a dit :

Reprenons :

Merci beaucoup, car je n'avais effectivement pas entièrement compris à 100% :unsure:

 

il y a une heure, Lazer a dit :

J'ajoute que l'utilisation des variables globales (et une fonction est en quelque sorte une variable au sens LUA) est à éviter autant que possible, car elles pourraient entrer en collision avec d'autres variables nommées à l'identique dans d'autres parties du code.

Une bonne pratique est de limiter la portée des variables à leur strict nécessaire, et à les passer en paramètres des fonctions quand nécessaire.

Cela devient de plus en plus important à mesure que notre code grossit, et qu'on réutilise des bouts de codes dans d'autres développements.

 

Un autre impact a lieu également sur les performances, car les variables globales elles sont rangées dans une super table globale nommée _G que l'interpréteur LUA doit parcourir à chaque fois qu'on fait appelle à ladite variable (ou fonction). C'est par conséquent plus lent que l'utilisation d'une variable (fonction) locale.

Pour une variable qui est appelée une fois par minute, ça ne change rien. Pour une boucle qui le ferait plusieurs fois par secondes, la différence devient sensible.

Merci, car c'est effectivement une question qui me trotte dans la tête. Grâce à mes petits débuts en QA je suis en train de supprimer mes variables globales qui étaient utilisées dans les scènes.

Mais du coup, que penser des variables non spécifiées "local" dans le code du QA, mais dans l'onglet "variable" du QA ? Si j'ai bien compris elles ne sont pas globales dans le sens LUA/Fibaro, mais d'un point de vue calcul et lenteur dans le QA comparé aux variables locales dans le code du QA ?

 

 

 

Modifié par Fredmas
Posté(e)

Thanks @jang

I thing I understood your main explanation.

 

 

The main thing not cristal clear for me is this sentence below and risks. Probably because I am not using children yet... I am too young in QA :D

il y a 38 minutes, jang a dit :

Also note that Fibaro sets the global 'quickApp' to self after :onInit() is done.  This means that we can use quickApp instead of passing around self to local functions if we are careful. It may not work if we deal with QuickAppChild instances as we then have many instances of "QuickApp" that we pass around...

Posté(e)
local n = 10000000 -- loop 10 million times...
local a = 1        -- our local variable
b = 1              -- our global variable
local t0 = os.clock()
for i=1,n do a=a+1 end
local t1 = os.clock()-t0
local t2 = os.clock()
for i=1,n do b=b+1 end
local t3 = os.clock()-t2
print("Local assignment ",n," times = ",t1,"s")
print("Global assignment ",n," times = ",t3,"s")
print("You saved ",t3-t1," seconds of your life")
[25.07.2021] [14:23:34] [DEBUG] [QUICKAPP400]: Local assignment 10000000 times = 0.579324 s
[25.07.2021] [14:23:34] [DEBUG] [QUICKAPP400]: Global assignment 10000000 times = 2.69236 s
[25.07.2021] [14:23:34] [DEBUG] [QUICKAPP400]: You saved 2.113036 seconds of your life

The HC3 is (or rather Lua is) quite fast.

  • Haha 1
Posté(e) (modifié)
il y a 6 minutes, Fredmas a dit :

Thanks @jang

I thing I understood your main explanation.

 

 

The main thing not cristal clear for me is this sentence below and risks. Probably because I am not using children yet... I am too young in QA :D

Yes, for  QuickAppChildren you first need a male QA and a female QA...

Modifié par jang
  • Haha 2
Posté(e)
il y a 2 minutes, jang a dit :

[25.07.2021] [14:23:34] [DEBUG] [QUICKAPP400]: Local assignment 10000000 times = 0.579324 s
[25.07.2021] [14:23:34] [DEBUG] [QUICKAPP400]: Global assignment 10000000 times = 2.69236 s
[25.07.2021] [14:23:34] [DEBUG] [QUICKAPP400]: You saved 2.113036 seconds of your life

The HC3 is (or rather Lua is) quite fast.

;) thank you for the demonstration @jang

 

 

 

il y a 3 minutes, jang a dit :

Yes, for  QuickAppChildren you need a male QA and a female QA...

:2: Unfortunately until now as I didn't investigate enough, Child in QA is close to be unknown for me. I will see later, even if I start to understand how it can be usefull for some application.

But I will try to keep in mind you advise linn to:

il y a 55 minutes, jang a dit :

This means that we can use quickApp instead of passing around self to local functions if we are careful.

 

×
×
  • Créer...