Aller au contenu

Questions de débutant en Quick Apps sur HC3


Messages recommandés

Posté(e) (modifié)

Bonjour à tous,

 

Allé, sans aucune honte j'ouvre ce sujet pour débutants qui ont lu le manuel QA de Fibaro mais qui n'ont pas trouvé leurs réponses :D

Je ne doit pas être le seul et je pense qu'au fil du temps, la liste des questions à venir dans ce topic pourrait peut-être (je l'espère) aider les autres débutants :wacko:

Lien vers le manuel Fibaro LUA

Lien vers le manuel Fibaro Quick Apps

 

 

#1 Question 1 : choix du type lors de la création d'un QA

Lien vers la réponse la plus utile

Lorsque l'on crée un QA simple le choix du type est plus facile/évident. Mais lorsque que l'on veut créer un QA qui fait plusieurs choses, vérifier des horaires, possède des boutons et des labels, change des variables globales ou locales, lance des scènes, active des modules (FGS/FGD/FGBS, etc.), comment faire pour choisir le bon type de QA lors de sa création ?

J'ai bien trouvé la liste existante, mais bien comprendre les conséquences du choix du type serait bien mieux.

Types of Quick App devices:

Binary sensor, Binary switch, Color controller, Door lock, Door sensor, Energy meter, Generic device, Flood sensor, Humidity sensor, Multilevel sensor, Multilevel switch, Player, Power sensor, Remote controller, Roller shutter, Smoke detector, Temperature sensor, Thermostat (auto, cool, heat), Weather, Wind sensor, Window sensor.

 

 

#2 Question 2 : faire tourner un QA en boucle pour vérifier l'heure et déclencher des actions

Lien vers la réponse la plus utile

Dans la continuité d'apprendre la philosophie des QA et leurs possibilités afin de remplacer les scènes petit à petit, je n'ai pas trouvé comment faire tourner un QA en boucle.

Je raisonne probablement encore trop en mode scène LUA, mais bon pas facile de changer du jour au lendemain. Alors je vais prendre un exemple pour illustrer la question et aider les autres débutants comme moi à se projeter en situation pour mieux comprendre.

La suite ici.

 

 

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

Lien vers la réponse la plus utile

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.

La suite ici.

 

 

#4 Question 4 : définir correctement la méthode de débogage dans la console d'un QA

Lien vers la réponse la plus utile

Cette fois, rien de bien compliqué, c'est davantage une question "café-philo" à propos des bonnes pratiques.

Après avoir lu le sujet rédigé par @Krikroff et du coup relu le manuel Quick Apps de Fibaro , il me reste quelques doutes quand au choix des différentes méthodes de de débogage dans la console d'un QA.

La suite ici.

 

 

#5 Question 5 : appeler une fonction B et attendre son résultat pour l'utiliser au sein d'une fonction A avant de continuer la suite du code du QA

Lien vers la réponse la plus utile

Dans une fonction A dans un QA j'appelle une autre fonction B, mais j'ai besoin d'attendre le résultat de cette autre fonction B avant de continuer à dérouler le reste de la fonction A.

Comment coder "proprement" pour que la ligne de 17 attende l'exécution et le résultat d'une fonction appelée à la ligne de code 16 ?

La suite ici.

 

 

#6 Question 6 : respecter de bonnes pratiques d'architecture logicielle pour le code d'un QA

Lien vers la réponse la plus utile et une deuxième

C'est davantage un sujet de partage, et à la rigueur de café-philo me concernant, qu'une question répondant à un besoin à proprement parler. Mais maintenant que mes QA sont stables et continuent d'être modifiés et améliorés, je me pose la question d'une meilleure architecture du code et de comment mieux le structurer tant pour la performance que pour sa compréhension et sa maintenance.

La suite ici.

 

 

 

 

Et un peu de la place pour les questions suivantes :P

 

Modifié par Fredmas
  • Like 1
  • Thanks 1
Posté(e) (modifié)

#1 Réponse 1 : choix du type lors de la création d'un QA

 

C'est un super QA que tu nous fait là :D

 

Sérieusement, j'ai aussi ce type de QA, notamment un qui s'appelle "Gestion Maison" et effectue des actions globales : éteindre toutes les lumières, baisser le chauffage, passer la maison en mode vacances, etc.

Du coup, aucun des types "simples" ne lui convient (ou plus exactement, aucun des type correspondant à des modules Z-Wave)

Dans ce cas, le type "Generic device" est tout indiqué.

Ou bien "Device Conroller" s'il aura des enfants (d'ailleurs les enfants peuvent être typés avec un type utile : temperature, etc).

 

En fait ça revient à faire un QA avec une certaine similitude à ce qu'étaient les Virtual Devices sur HC2.

 

Les types Generic et Device Controller n'auront pas d'action associé au clic sur leur icône, ils n'afficheront rien sous leur icône. Il faut les ouvrir (pour afficher leur vue et accéder aux boutons sliders et labels)

Par ailleurs, aussi étrange que cela puisse paraitre, on ne peut pas (encore ?) changer leur icône... sauf à utiliser le hack assez simple déjà partagé plusieurs fois sur le forum.

 

image.png.9d2a1d7c6e16c74026b362696fc360c0.png

 

Rappel : évidemment, autant que possible, il faut utiliser un des types qui correspond à un module Z-Wave (lumière, température, etc) afin de respecter la logique Fibaro, et surtout en bénéficier : à l'usage un QA correctement typé s'utilise exactement de la même façon qu'un module Z-Wave dans la box ou sur l'application mobile. Très pratique.

 

Dernier point : une fois affecté, un module ne peut pas changer de type.

Astuce de contournement : il faut exporter le QA, modifier son type à la main avec un éditeur de texte dans le JSON, puis le réimporter.

 

Modifié par Lazer
  • Like 3
  • Thanks 1
  • Upvote 1
Posté(e) (modifié)

:2: super QA ça je ne sais pas, mais comme dit ailleurs maintenant que j'ai fait quelques trucs (probablement basiques pour les spécialistes) en LUA, je commence l'apprentissage des QA.

Effectivement ce QA serait dédié à la gestion de la piscine, et probablement dans l'esprit du tien pour la maison: par exemple chauffage (FGBS) et filtration (FGS), en fonction des températures (FGBS), des horaires, de boutons virtuels auto/manuel, du mode vacances/présence, etc.

Le deuxième QA, quand j'aurai assimilé la philosophie, sera pour transformer certaines scènes LUA faites pour la gestion de maison, avec l'expérience du premier non vital ;)

 

 

Merci pour ta réponse, je viens d'en créer un pour essayer et effectivement le type "Generic device" dont tu parles semble tout indiqué.

Dommage pour l'icône, mais ce n'est pas le plus important. En tout cas je comprends un peu mieux le fonctionnement de ce "generic" et du coup des autres, avec le fait de devoir l'ouvrir ou pas pour l'utiliser par exemple.

Du coup pour les types "simples" ça veut dire que la gestion de ce qui se passe en lien avec l'icône par exemple ou les notifications, est gérée de manière transparente (non visible dans le main en tout cas) en lien avec les méthodes existantes lors de la création du QA?

 

Après j'avoue que le type "Device Conroller" m'interpelle, en tout cas la porte que tu ouvres lorsque tu en parles en citant la possibilités d'enfants :D

Mais bon je vais déjà essayer d'un faire un "generic" correctement d'abord...

Modifié par Fredmas
Posté(e) (modifié)

"transparante", presque, car c'est quand même à ton QA de changer ses propres proriétés (le champ value principalement). Mais rien que modifier ce champ value, par exemple true/false si c'est du type binary, ou 0-99 si multilevel, alors c'est la box qui va adapter l’icône en conséquence et le visuel du module.

 

Alors les enfants justement, pour moi c'est tout indiqué dans ton QA.

Justement, tu parles de piscine, avec des FGS (donc "binary switch") des capteurs (donc "temperature semsor"), etc


Et je me rend compte que tu n'as pas compris la philosophie des QA, tu raisonnes encore sous forme de VD.
 

Ton QA Generic ou Device Controller ne doit contenir sur des choses qui ne peuvent pas figurer dans un QA typé.

 

Du coup, tu devrais avoir un QA parent (Device controller) qui n'affiche rien, si ce n'est un bouton pour créer ses enfants. Une fois fait, tu peux le cacher dans l'interface. Du coup son icone par défaut n'a plus aucune importance.

Et les enfants, seront typés chacun comme il faut, te permettant de piloter ta pompe, surveiller la température, etc.

 

Si tu regardes mes QA GCE IPX800 & EDRT2, Surveillance Station, Netatmo, etc, ils fonctionnent sur ce principe. Une fois que le parent a créé ses enfants, il peut être caché, on n'en n'aura plus jamais besoin pour un usage quotidien de la box.

 

Bon sauf si tu as des infos à afficher, ou boutons spécifiques à créer, auquel cas ils ne correspondent à aucun type de QA prédéfini. Dans ce cas, le QA générique garde son intérêt.

 

Modifié par Lazer
Posté(e)
Il y a 9 heures, Lazer a dit :

Et je me rend compte que tu n'as pas compris la philosophie des QA, tu raisonnes encore sous forme de VD.

C'est fort probable oui... ;)

 

Il y a 9 heures, Lazer a dit :

Alors les enfants justement, pour moi c'est tout indiqué dans ton QA.

Justement, tu parles de piscine, avec des FGS (donc "binary switch") des capteurs (donc "temperature semsor"), etc

(...)

Du coup, tu devrais avoir un QA parent (Device controller) qui n'affiche rien, si ce n'est un bouton pour créer ses enfants. Une fois fait, tu peux le cacher dans l'interface. Du coup son icone par défaut n'a plus aucune importance.

Et les enfants, seront typés chacun comme il faut, te permettant de piloter ta pompe, surveiller la température, etc.

 

Si tu regardes mes QA GCE IPX800 & EDRT2, Surveillance Station, Netatmo, etc, ils fonctionnent sur ce principe. Une fois que le parent a créé ses enfants, il peut être caché, on n'en n'aura plus jamais besoin pour un usage quotidien de la box.

 

Bon sauf si tu as des infos à afficher, ou boutons spécifiques à créer, auquel cas ils ne correspondent à aucun type de QA prédéfini. Dans ce cas, le QA générique garde son intérêt.

 

Bon ben je vais regarder cette histoire d'enfants alors... :unsure:

Sachant qu'il ne me semblait pas avoir besoin de QA pour commander le FGS qui commandera la pompe, ni le FGBS qui remontera la température de la pac et commandera son relais on/off.

Le but étant que le QA s'occupe de tout seul, principe de l'automatisme et pas de la télécommande, je pensais principalement gérer le mode absent, ou 2 ou 3 boutons pour forcer des mode auto/manuel éventuellement. Puisqu'en dehors des 2 ou 3 boutons, en mode scène LUA je sais déjà comment le faire.

Mais comme tu le dis, peut-être n'ai-je pas le bon mode de raisonnement QA vs VD...:D

Posté(e)

Pas sûr de bien comprendre ton besoin exact pour ta piscine.

C'est difficile de te conseiller, il faudra peut être aussi expérimenter par toi même.

Car mettre en œuvre des modules enfants, ce n'est pas si simple, surtout quand on débute.

Il est surement plus sage de commencer par un QA simple, au moins tu arriveras rapidement à un résultat fonctionnel.
Puis plus tard, éventuellement, remettre en cause ce que tu as fait en te rendant compte qu'il y a moyen de faire mieux (child device ou pas, ça restera à voir)

Posté(e) (modifié)

Oui merci @Lazer.

A la rigueur ce topic est plus pour des besoins de compréhension du fonctionnement des QA pour débutant que de résoudre à proprement parler mon besoin, ou chaque besoin individuel. Même si tout est lié.

Je pense avoir mieux compris les "types", et je vais essayer avec un "generic" de construire mon premier vrai QA répondant à ce besoin comme un premier exercice à mettre en prod.

Malgré tout je suis en train de lire/relire le topic traitant des enfants pour commencer à comprendre. Pas toujours simplement de changer son mode de raisonnement :D

Modifié par Fredmas
Posté(e)

Je viens de finir de lire le topic traitant des enfants... Heuuu comment dire, même si j'essaie d'apprendre vite, il me permet de mesurer le gouffre de connaissance et de confort de codage que les habitués ont, comparé à moi qui me suis mis au LUA il y a 1 mois après quelques années en mode bloc.

Donc oui, étape par étape, après avoir lu et relu, codé et recodé quelques scènes en LUA, je me sens plus à l'aise, mais il y a encore pas mal de chemin à parcourir.

Je vais démarré déjà un premier QA conséquent (pour moi) en Generic Device, et une fois à l'aise je tenterai de voir ce que peut m'apporter cette gestion d'enfants.

 

Donc pour revenir à ce topic typé débutants :D, et je l'espère pas utile uniquement pour moi in fine, après la compréhension des types je reviendrai pour d'autres découvertes :ph34r:

Mais avec méthode et classe bien entendu :2: Bon ok je sors... <_<

  • Like 2
  • 3 semaines après...
Posté(e) (modifié)

#2 Question 2 : faire tourner un QA en boucle pour vérifier l'heure et déclencher des actions

 

Dans la continuité d'apprendre la philosophie des QA et leurs possibilités afin de remplacer les scènes petit à petit, je n'ai pas trouvé comment faire tourner un QA en boucle.

Je raisonne probablement encore trop en mode scène LUA, mais bon pas facile de changer du jour au lendemain. Alors je vais prendre un exemple pour illustrer la question et aider les autres débutants comme moi à se projeter en situation pour mieux comprendre.

Un exemple pour un cas concret :

J'ai une scène "temps" qui sert qu'à vérifier toutes les 60s (avec un trigger "date/matchInterval/cron") la nécessité d'agir sur des variables globales ou d'autres actions (oui pour le moment j'utilise encore les variables globales :P). Dans cette scène "temps" je vérifie les variables, les horaires, coucher de soleil, vacances, les modes marche/auto, etc. Et dans une autre scène "volets" pour mon exemple je gère toutes les actions nécessaires à l'automatisation des volets, et fonction de ce que lui dit la scène "temps".

Même si j'ai des idées et possibilités d'optimisation, pour l'instant tout fonctionne bien en toute autonomie sans que je m'en occupe, en dehors de cas de vie exceptionnels non encore implémentés.

 

Mais dans l'apprentissage des QA, j'essaie de réfléchir à une autre alternative. Si je savais obtenir dans un QA le même résultat que ce que fait ce trigger de type "date/matchInterval/cron" dans une scène, ce que j'appelle faire tourner en boucle, dans mon exemple cela permettrait d'avoir un QA qui surveillerait quand il faut faire quoi, exécuterait les actions, avec des boutons de paramétrage d'horaires exceptionnels, et moins de variables globales :D
Evidemment j'ai pris cet exemple simple pour nous mettre en situation et apprendre cette possibilité de boucle temporelle d'un QA, mais le but est bien de comprendre les possibilités pour davantage d'applications potentielles.

 

Avant toute chose j'ai commencé par lire les 2 topics Tests d'utilisation de /api/refreshStates et Quick App - Evénements, mais je ne suis pas sûr que cela corresponde. Déjà ils ont l'air complexes, et surtout ils servent à vérifier plus de chose que simplement faire tourner le QA toutes les 60s comme décrit ici.

 

Modifié par Fredmas
  • Like 1
Posté(e) (modifié)
function intervalRunner(seconds,fun)
  local nxt,ref=nil,{}
  local function loop()
    if fun()==':EXIT' or ref[1]==nil then return end 
    nxt=nxt+seconds     
    ref[1] = setTimeout(loop,1000*(nxt-os.time()))
  end
  local t = (os.time() // seconds) * seconds
  nxt=t+seconds
  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

 

local function test() print("ping") end

 

intervalRunner(60,test) will run test every minute, on the minute. 00:05:00, 00:06:00, 00:07:00 etc.

intervalRunner(30,test) will run test every 30s, on the even 30s. 00:00:30, 00:01:00, 00:01:30 etc.

intervalRunner(3600,test) will run test every hour, on the hour. 03:00:00, 04:00:00, 05:00:00, etc.

 

It's nice because it syncs to even periods and it doesn't drift.

 

Ex.

local function checkGlobals()

   ....

end

 

function QuickApp:onInit()

    intervalRunner(60,checkGlobals)

end

 

If the function returns the string ':EXIT', the loop will stop. You can also stop it with calling stopIntervalRunner(ref) with the ref that intervalRunner returns.

Modifié par jang
  • Like 2
Posté(e) (modifié)

Thanks a lot @jang for your quick answer ;)

 

If I understand in the good way the philosophy of your explanation:

  • the function fun is the one I want to run every time
  • in onInit() I define the time loop I want and the function to run for each loop
  • the function intervalRunner is the loop function which will run the function fun each time it is defined in onInit()
  • if the function fun returns :EXIT it will stop the function intervalRunner, so the loop
  • if I call the function stopIntervalRunner, ref[1] will take nil, and if ref[1]==nil the function intervalRunner will stop the function intervalRunner, so the loop

 

Is my newbie understanding good? :D

Modifié par Fredmas
  • Like 1
Posté(e)

@Fredmas en fait tu es en train de réinventer la roue ;)

 

Ce que tu cherches à faire, c'est exactement ce que fait déjà GEA, ou l'exemple de @jang

 

GEA fait bien plus évidemment, mais ça fonction de base c'est de surveiller à intervalle régulier l'état de plusieurs conditions, et agir (ou pas) en conséquence.

Maintenant si tu veux juste apprendre pour le plaisir de coder en LUA, dans ce cas tu peux effectivement écrire ta propre boucle, comme dans l'exemple intervalRunner(), qui sera plus efficace (plus précis) que GEA (qui est précis à 30 secondes près). Dans l'esprit, cela ressemble beaucoup au Scheduler qui existait sur HC2 v3.

Posté(e) (modifié)

Oui tu as raison @Lazer, je m'étais fait la remarque d'ailleurs.

Mais comme tu as compris que j'aime bien comprendre ce que je fais, tu as raison c'est aussi un peu par plaisir d'apprendre et de coder mon truc que j'essaie de faire des QA.

J'ai bien pensé prendre ta version HC3 de GEA, mais bon dans un premier temps voir ma phrase précédente :P

Alors j'essaie d'évoluer petits bouts par petits bouts de temps en temps le week-end, et en partageant mes questions/réponses dans ce topic au cas où cela aiderait d'autres membres (évidemment ce n'est pas toi ou @jang que ça va aider vu votre niveau élevé en QA :lol: )

Modifié par Fredmas
  • Like 1
Posté(e)

After having read several times since yesterday, even if I don't understand at 100% yet the way to work of both setTimeout, I think I understood roughly the way to loop from the function intervalRunner.

But in the code I don't understand when or how (which line) intervalRunner is asking to run the function "fun" each time? :(

Probably a lack of knowledge from my side about function and QA... -_-

When we start the QA in onInit method we call the function intervalRunner with 2 parameters (60 and fun), received by intervalRunner as 2 arguments to be used. But I don't see where intervalRunner is running the function "fun" in this example :unsure:

  • 2 semaines après...
Posté(e) (modifié)

Après avoir de nouveau passé pas mal de temps à relire (Manuals Fibrao LUA, std:exception: 'Timeout', Settimeout, example-of-settimeout) et re-re-réfléchir :D je pense avoir mieux compris le fonctionnement de timeout avec ses deux paramètres, et la majorité des lignes de code de l'exemple de @jang de la fonction intervalRunner.

Je pense avoir mieux compris le fonctionnement de la fonction local loop() avec le setTimeout, nxt et ref, après avoir fait plusieurs essais avec 'print' dans une petite scène en regardant les résultats dans la console.

 

Le principal point que je n'arrive toujours pas à comprendre et à intégrer dans ma petite tête, c'est quand la fonction loop() va appeler la fonction fun() ou test() ou checkGlobals() :huh:

Faut-il ajouter une ligne avant le 'end' de la fonction loop() pour qu'à chaque boucle elle lance l'exécution de la fonction fun() ou test() ou checkGlobals() ? :wacko:

Dans ce cas cela ressemblerait-il à l'exemple ci-dessous, avec l'appel de fun() à la fin de loop(), avec seconds remplacé par 60 et fun remplacé par checkGlobals lors du démarrage du QA :

function checkGlobals()
	...
end

function intervalRunner(seconds,fun)
	...
	local function loop()
		...
		fun()
	end
	...
end

function stopIntervalRunner(ref)
	...
end

function QuickApp:onInit()
	intervalRunner(60,checkGlobals)
end

 

Ou alors je n'ai définitivement rien compris ? :20:

 

Modifié par Fredmas
Posté(e) (modifié)

Bon après des essais simples avec un 'print' pour voir le résultat, je me réponds tout seul :D

Pas la peine d'ajouter l'appel à la fonction fun() avant le 'end' de la fonction loop(). Parce que, et je n'ai toujours pas compris pourquoi, la fonction intervalRunner lance bien (et uniquement) la fonction appelée dans le onInit (alors que je croyais quelle tournerait en boucle sur elle-même), et appeler la fonction fun() dans la fonction loop() d'intervalRunner double tout simplement le lancement de la fonction checkGlobalVariables() dans mon exemple ;)

 

Voilà mon essai, qui fonctionne (merci @jang pour la proposition de code), qui donne :

function checkGlobalVariables()
	print("this function will check global variables")
	-- hereafter the function I want to launch regularly thanks to the loop in the function IntervalRunner
end

function trial()
    print("trial to check if this function is also concerned by the loop from intervalRunner")
end

function intervalRunner(seconds,fun)
	local nxt,ref=nil,{}
	local function loop()
		if fun()==':EXIT' or ref[1]==nil then
			return
		end
		nxt=nxt+seconds
		ref[1] = setTimeout(loop,1000*(nxt-os.time()))
	end
	local t = (os.time() // seconds) * seconds
	nxt=t+seconds
	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()
	intervalRunner(60,checkGlobalVariables)
end

Et toutes les minutes la console affiche bien : "this function will check global variables".

Si j'ajoute return ":EXIT" ou que j'appelle stopIntervalRunner() à la fin de la fonction checkGlobalVariables, ça marche une seule fois puis s'arrête B)

Si je remplace checkGlobalVariables par trial dans le onInit, ça change de fonction dans la loop intervalleRunner.

Donc grâce à la fonction intervaleRunner(), la fonction checkGlobalVariables (et uniquement celle-ci) dans le QA fonctionne en loop avec le délai prescrit dans onInit().

 

A+

Fred

Modifié par Fredmas
  • Like 2
Posté(e)

Thank you @jang I am still learning :P

And there are some points I don't understand perfectly:

 

#1 ref[1] = setTimeout(loop,1000*(nxt-os.time()))

I understood that ref is a table, and now after some tests I am understanding that each time the operation after '=' is successfull, the table ref is incremented about 1 more. Am I correct about the way to work of this incrementation?

It's what I see putting a print(ref[1]) and checking each loop the result in the console: 1, then 2, then 3, then 4, etc.

 

#2 local t = ( os.time() // seconds) * seconds

I understand the floor division. But what is the purpose to put it here and not simply using os.time() for next operation: nxt=nxt+os.time()? To be sure not to drift about some tenths of a second several times?

 

#3 ref[1] = setTimeout(loop,1000*(nxt-os.time()-(seconds > 3600 and 3600 or 0)))

I don't understand the syntax of the last part of this sentence: "(seconds > 3600 and 3600 or 0)". Is it a LUA optimization for the way to write something like "(if seconds > 3600 then 3600 else 0)"?

 

 

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

Thank you @jang I am still learning : P

And there are some points I don't understand perfectly:

 

# 1 ref [1] = setTimeout (loop, 1000 * (nxt-os.time ()))

I understood that ref is a table, and now after some tests I am understanding that each time the operation after '=' is successfull, the table ref is incremented about 1 more. Am I correct about the way to work of this incrementation?

It's what I see putting a print (ref [1]) and checking each loop the result in the console: 1, then 2, then 3, then 4, etc.

setTimeout() returns a reference that can be used to cancel the timer with clearTimeout(ref)

The reference happens to look like an incrementing number but it's internal to setTimeout.

 

intervalRunner needs to return a reference so that we can stop it with stopIntervalRunner(ref).

We can't return setInterval's reference directly as we generate a new reference each loop when we call setTimeout.

Instead we return a table with one position where we store the last reference from setTimeout.

Each loop in intervalRunner updates the table with the new reference from setTimeout.

 

If (and when) we call stopIntervalRunner(ref), ref will be the table and it will contain the last setTimeout reference.

That reference we can then use to cancel the ongoing setTimeout call with clearTimeout(ref[1])

 

il y a 37 minutes, Fredmas a dit :

# 2 local t = (os.time () // seconds) * seconds

I understand the floor division. But what is the purpose to put it here and not simply using os.time () for next operation: nxt = nxt + os.time ()? To be sure not to drift about some tenths of a second several times?

It's so that the time interval should start on even intervals - 60 it starts on even minutes, 15 it starts on even 15s (00:15,00:30 etc), 3600 it starts on even hours.

 

The drift is taken care of with the statement

setTimeout ( loop , 1000 * ( nxt - os . time ()))

 

il y a 37 minutes, Fredmas a dit :

# 3 ref [1] = setTimeout (loop, 1000 * (nxt-os.time () - (seconds> 3600 and 3600 or 0)))

I don't understand the syntax of the last part of this sentence: "(seconds> 3600 and 3600 or 0)". Is it a LUA optimization for the way to write something like "(if seconds> 3600 then 3600 else 0)"?

Yes, but it's an expression so it returns a value. if-then-else is a statement and doesn't return a value.

We can't do

local b = 100 * (if a == 1 then return  9 else return 10 end)

but it would have been nice ;)

  • Like 1
Posté(e)

Thank you @jang

 

#1: cristal clear ;)

 

#2: cristal clear ;)

 

#3: clear but:

I understand your explanantion about the difference between expression and possibility to return a value ;)

But to be sure I understood the calculation inside, this means:

- if seconds = 60 in onIntit()  ==> ref [1] = setTimeout (loop, 1000 * (nxt-os.time () - 0)

- if seconds = 3601 in onIntit() ==> ref [1] = setTimeout (loop, 1000 * (nxt-os.time () - 3600), and in this case sometimes the result of (nxt-os.time () - 3600) can be a negative number :huh:

Am I correct? If yes, I don't understand the purpose to substract 3600 when seconds is more than 3600.

Posté(e) (modifié)
Il y a 12 heures, Fredmas a dit :

But to be sure I understood the calculation inside, this means:

- if seconds = 60 in onIntit () ==> ref [1] = setTimeout (loop, 1000 * (nxt-os.time () - 0)

- if seconds = 3601 in onIntit () ==> ref [1] = setTimeout (loop, 1000 * (nxt-os.time () - 3600), and in this case sometimes the result of (nxt-os.time ( ) - 3600) can be a negative number: huh:

Am I correct? If yes, I don't understand the purpose to substract 3600 when seconds is more than 3600.

Now you are starting to put pressure on me :)

Well it turns out that 

print(os.date("%c",0))

>Thu Jan  1 01:00:00 1970

So it starts to count at 1 o'clock Jan 1 1970, not at 0 o'clock (midnight). This means that my alignment calculation becomes 1 hour off when we align for time >= 1 hour.

My "hack" to subtract of 1 hour makes it work for time of 0-3600 seconds but not for 2 hours.

After some thinking this is more correct and allow it to align up to better for hours.

function intervalRunner(seconds,fun)
  local nxt,ref=nil,{}
  local function loop()
    if fun()==':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  -- remove 1 hour from epoch 0 for align calc
  nxt=t+seconds+3600                                  -- add back to start time
  ref[1] = setTimeout(loop,1000*(nxt-os.time()))
  return ref
end

However, alignment is a tricky concept for time - when we have intervals of hours we maybe would like to align it from midnight. 

If we do intervalRunner(2*3600,fun) it won't align on even hours which may be what we had expected. It can become 3,5,7,... (reason being, I guess, leap years that don't make hours always align from midnight)

We can also skip the whole alignment and simplify the code

function intervalRunner(seconds,fun)
  local nxt,ref=os.time(),{}
  local function loop()
    if fun()==':EXIT' or ref[1]==nil then return end 
    nxt=nxt+seconds     
    ref[1] = setTimeout(loop,1000*(nxt-os.time()))
    return ref
  end
  return loop()
end

..it will still be drift free.

Modifié par jang
  • Like 1
Posté(e) (modifié)
Il y a 14 heures, jang a dit :

Now you are starting to put pressure on me :)

No, no, no, absolutely not :D

Just I like to understand what I do and most of all how it works, doing some test and not simply reuse a code given  ;)

As I said I am a QA beginner, and I need to learn and understand to be autonomous later, sharing in same time with other beginners.

 

 

Il y a 14 heures, jang a dit :

After some thinking this is more correct and allow it to align up to better for hours.


function intervalRunner(seconds,fun)
  local nxt,ref=nil,{}
  local function loop()
    if fun()==':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  -- remove 1 hour from epoch 0 for align calc
  nxt=t+seconds+3600                                  -- add back to start time
  ref[1] = setTimeout(loop,1000*(nxt-os.time()))
  return ref
end

However, alignment is a tricky concept for time - when we have intervals of hours we maybe would like to align it from midnight. 

If we do intervalRunner(2*3600,fun) it won't align on even hours which may be what we had expected. It can become 3,5,7,... (reason being, I guess, leap years that don't make hours always align from midnight)

It sounds good.

I don't see a real issue not to start from 0:00 for big loop of hours but from 1:00, even if I understand the wish to start from midnight for some people.

 

 

Il y a 14 heures, jang a dit :

We can also skip the whole alignment and simplify the code

But it would be a pity. Your alignment proposal is a good waf idea  :60:

 

 

Il y a 14 heures, jang a dit :

..it will still be drift free.

Yes, perfect :P

 

 

 

Thanks a lot for being patient and your explanation @jang :74:

 

Modifié par Fredmas
  • Like 1
Posté(e) (modifié)

Hello,

Tout fonctionne en boucle, sauf pour un point : l’utilisation de variables provenant du QA au lieu des VG :unsure:

Et oui l'étape suivante logique est que le QA soit autonome sans utiliser les Variables Globales ;)

 

J'ai réalisé beaucoup d'essai pour trouver la solution mais je n'y suis pas arrivé. Description :

Si dans la fonction checkVariables() j'appelle une variable avec fibaro.getGlobalVariable("var") ça fonctionne.

Si dans la fonction checkVariables() j'appelle une variable avec self:getVariable("var") ça ne boucle pas. Le print fonctionne une fois (normal la fonction est désormais appelée par le onInit), mais le print ne fonctionne plus ensuite.

 

Une logique que j'aurais mal comprise ? :huh:

 

 

Ci-dessous ça fonctionne. Je vois le print toutes les 10s.

function QuickApp:onInit()
  self:debug("onInit")
  intervalRunner(10,checkVariables)
end

function checkVariables()
  print(fibaro.getGlobalVariable("variableVG"))
  -- hereafter the function I want to launch regularly thanks to the loop in the function IntervalRunner
end

function intervalRunner(seconds,fun)
  local nxt,ref=nil,{}
  local function loop()
    if fun()==':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

 

Ci-dessous ça fonctionne. Je vois le print toutes les 10s.

function QuickApp:onInit()
  self:debug("onInit")
  intervalRunner(10,checkVariables)
  checkVariables(self)
end

function checkVariables(self)
  print(fibaro.getGlobalVariable("variableVG"))
  -- hereafter the function I want to launch regularly thanks to the loop in the function IntervalRunner
end

function intervalRunner(seconds,fun)
  local nxt,ref=nil,{}
  local function loop()
    if fun()==':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

 

Ci-dessous ça ne fonctionne pas. Je vois le print une fois, normal puisque onInit(). Et ensuite la boucle commence et s'arrête s'arrête dès qu'elle rencontre un self: comme par exemple print(self:getVariable("variableQA"))

function QuickApp:onInit()
  self:debug("onInit")
  intervalRunner(10,checkVariables)
  checkVariables(self)
end

function checkVariables(self)
  print(self:getVariable("variableQA"))
  -- hereafter the function I want to launch regularly thanks to the loop in the function IntervalRunner
end

function intervalRunner(seconds,fun)
  local nxt,ref=nil,{}
  local function loop()
    if fun()==':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

 

Modifié par Fredmas
Posté(e)

Yes, because the checkVariables needs 'self' that's not available when called from intervalRunner - so it crashes (silently)

Solution:

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

 

That setTimeout crashes silently when the function crash is really a problem and makes many errors go undetected.

You can add my fibaroExtra.lua files to your QA and you will see that you get an error when the checkVariables try to call self:getVariable (it also protects success/error handlers net.HTTPClient() )

  • Like 1
Posté(e)

:60:

yes it works! Thank you @jang for the explanation and the link.

 

But in this case now, writting return":EXIT" at the end of checkVariables doesn't work anymore, so I tried to replace in function intervalRunner (seconds,fun):

 

if fun()==":EXIT" by if function()fun(self)end==":EXIT" but it doesn't work :angry:

if fun()==":EXIT" by if fun(self)==":EXIT" but it doesn't work :angry:

if fun()==":EXIT" by if function()checkVariables(self)end==":EXIT" but it doesn't work :angry:

if fun()==":EXIT" by if checkVariables(self)==":EXIT" but it doesn't work :angry:

if fun()==":EXIT" by if checkVariables()==":EXIT" but it doesn't work :angry:

 

Posté(e) (modifié)

No no no, the solution is to do this in your code

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

intervalRunner shouldn't care what function it is given or if it needs self or not. No need to change intervalRunner.

Modifié par jang
×
×
  • Créer...