Fredmas Posté(e) le 11 juin 2021 Signaler Posté(e) le 11 juin 2021 (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 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 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 Modifié le 21 décembre 2021 par Fredmas 1 1
Lazer Posté(e) le 11 juin 2021 Signaler Posté(e) le 11 juin 2021 (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à 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. 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é le 11 juin 2021 par Lazer 3 1 1
Fredmas Posté(e) le 12 juin 2021 Auteur Signaler Posté(e) le 12 juin 2021 (modifié) 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 Mais bon je vais déjà essayer d'un faire un "generic" correctement d'abord... Modifié le 12 juin 2021 par Fredmas
Lazer Posté(e) le 12 juin 2021 Signaler Posté(e) le 12 juin 2021 (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é le 12 juin 2021 par Lazer
Fredmas Posté(e) le 12 juin 2021 Auteur Signaler Posté(e) le 12 juin 2021 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... 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...
Lazer Posté(e) le 13 juin 2021 Signaler Posté(e) le 13 juin 2021 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)
Fredmas Posté(e) le 14 juin 2021 Auteur Signaler Posté(e) le 14 juin 2021 (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 Modifié le 14 juin 2021 par Fredmas
Fredmas Posté(e) le 14 juin 2021 Auteur Signaler Posté(e) le 14 juin 2021 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 , 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 Mais avec méthode et classe bien entendu Bon ok je sors... 2
Fredmas Posté(e) le 3 juillet 2021 Auteur Signaler Posté(e) le 3 juillet 2021 (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 ). 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 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é le 15 juillet 2021 par Fredmas 1
jang Posté(e) le 3 juillet 2021 Signaler Posté(e) le 3 juillet 2021 (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é le 3 juillet 2021 par jang 2
Fredmas Posté(e) le 3 juillet 2021 Auteur Signaler Posté(e) le 3 juillet 2021 (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? Modifié le 3 juillet 2021 par Fredmas 1
Lazer Posté(e) le 3 juillet 2021 Signaler Posté(e) le 3 juillet 2021 @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.
Fredmas Posté(e) le 3 juillet 2021 Auteur Signaler Posté(e) le 3 juillet 2021 (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 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 ) Modifié le 15 juillet 2021 par Fredmas 1
Fredmas Posté(e) le 4 juillet 2021 Auteur Signaler Posté(e) le 4 juillet 2021 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
Fredmas Posté(e) le 14 juillet 2021 Auteur Signaler Posté(e) le 14 juillet 2021 (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 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() 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() ? 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 ? Modifié le 15 juillet 2021 par Fredmas
Fredmas Posté(e) le 15 juillet 2021 Auteur Signaler Posté(e) le 15 juillet 2021 (modifié) Bon après des essais simples avec un 'print' pour voir le résultat, je me réponds tout seul 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 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é le 16 juillet 2021 par Fredmas 2
Fredmas Posté(e) le 18 juillet 2021 Auteur Signaler Posté(e) le 18 juillet 2021 Thank you @jang I am still learning 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)"?
jang Posté(e) le 18 juillet 2021 Signaler Posté(e) le 18 juillet 2021 il y a 37 minutes, Fredmas a dit : Thank you @jang I am still learning 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 1
Fredmas Posté(e) le 18 juillet 2021 Auteur Signaler Posté(e) le 18 juillet 2021 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 Am I correct? If yes, I don't understand the purpose to substract 3600 when seconds is more than 3600.
jang Posté(e) le 19 juillet 2021 Signaler Posté(e) le 19 juillet 2021 (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 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é le 19 juillet 2021 par jang 1
Fredmas Posté(e) le 19 juillet 2021 Auteur Signaler Posté(e) le 19 juillet 2021 (modifié) Il y a 14 heures, jang a dit : Now you are starting to put pressure on me No, no, no, absolutely not 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 Il y a 14 heures, jang a dit : ..it will still be drift free. Yes, perfect Thanks a lot for being patient and your explanation @jang Modifié le 19 juillet 2021 par Fredmas 1
Fredmas Posté(e) le 22 juillet 2021 Auteur Signaler Posté(e) le 22 juillet 2021 (modifié) Hello, Tout fonctionne en boucle, sauf pour un point : l’utilisation de variables provenant du QA au lieu des VG 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 ? 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é le 22 juillet 2021 par Fredmas
jang Posté(e) le 22 juillet 2021 Signaler Posté(e) le 22 juillet 2021 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() ) 1
Fredmas Posté(e) le 22 juillet 2021 Auteur Signaler Posté(e) le 22 juillet 2021 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 if fun()==":EXIT" by if fun(self)==":EXIT" but it doesn't work if fun()==":EXIT" by if function()checkVariables(self)end==":EXIT" but it doesn't work if fun()==":EXIT" by if checkVariables(self)==":EXIT" but it doesn't work if fun()==":EXIT" by if checkVariables()==":EXIT" but it doesn't work
jang Posté(e) le 22 juillet 2021 Signaler Posté(e) le 22 juillet 2021 (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é le 22 juillet 2021 par jang
Messages recommandés