Aller au contenu
Lazer

Virtual Device Et Main Loop

Recommended Posts

Voici quelques résultats de mes recherches sur les Main Loop d'un périphérique virtuel, en espérant que ça puisse aider les développeur de super modules.

 

Le code LUA d'une Main Loop tourne en boucle avec un sleep de 3s.

Si la main loop bouffe trop de ressources (allocation de variables locales à  répétition), la HC2 finit par tuer le processus, et on est obligé de relancer la main loop en sauvegardant à  nouveau le Virtual Device.

 

Une solution a été trouvée par Krikroff, en gros, le principe est de créer une fonction et d'appeler celle-ci à  chaque boucle, afin de ne pas ré-allouer les variables à  chaque passage.

Il faut s'inspirer de ses Virtual Device pour comprendre (genre Update Notifier). Malgré cela j'ai découvert que le module Update Notifier est affecté par un bug qui finit par arriver au but de plusieurs jours (en fonction de la fréquence de rafraichissement). C'est la fonction Net.FHttp() qui est responsable, car trop gourmande en ressources.

 

Démonstration :

 

Dans un premier temps, on crée une main loop toute simple, avec des lignes de codes qui s’enchainent, un un fibaro:sleep() à  la fin, sachant que le temps d'attente total fera 3 secondes de plus, car le code interne de la HC2 effectue automatiquement un sleep de 3s :

--------------------------------------------------
-- Exemple de Main loop
--------------------------------------------------

-- System variables
local selfID = fibaro:getSelfId()
local ip = fibaro:get(fibaro:getSelfId(), 'IPAddress')
local port = fibaro:get(fibaro:getSelfId(), 'TCPPort')
local Web = Net.FHttp(ip, port)

-- Main code
local payload = "/url/a/appeler?avec&des&parametres"
response, status, errorCode = self.Web:GET(payload)
if tonumber(status) == 200 and tonumber(errorCode) == 0 then
	if (response ~= nil) then
		-- Actions à  effectuer
		-- ...
	else
		fibaro:debug('Error : Can not connect to Web server, empty response')
	end
else
	fibaro:debug('Error : Can not connect to Web server, status='..status..', errorCode='..errorCode)
end

-- Wait 60s
fibaro:sleep(60*1000)

Normalement, cette main loop sera tuée au bout de quelques heures, car les variables ne sont pas libérées, et surtout réallouées à  chaque passage dans la boucle, donc on fini par dépasser une limite de mémoire utilisée.

 

 

Dans un second temps, on applique la recette de Krikroff, en créant une sorte d'objet qui est défini une seule fois. Cet objet contient des variables (elles sont également définies une seule fois), et une ou plusieurs fonctions. La fonction principale est appelée à  chaque passage dans la boucle :

--------------------------------------------------
-- Exemple de Main loop
--------------------------------------------------

if (MyObject == nil) then MyObject = {
	-- System variables
	ip = fibaro:get(fibaro:getSelfId(), 'IPAddress'),
	port = fibaro:get(fibaro:getSelfId(), 'TCPPort'),
	-- Main code
	main = function(self)
		local Web = Net.FHttp(self.ip, self.port)
		local payload = "/url/a/appeler?avec&des&parametres"
		response, status, errorCode = Web:GET(payload)
		if tonumber(status) == 200 and tonumber(errorCode) == 0 then
			if (response ~= nil) then
				-- Actions à  effectuer
				-- ...
			else
				fibaro:debug('Error : Can not connect to Web server, empty response')
			end
		else
			fibaro:debug('Error : Can not connect to Web server, status='..status..', errorCode='..errorCode)
		end
		-- Wait 60s
		fibaro:sleep(60*1000)
	end
}
fibaro:debug("Function successfully loaded in memory")
end

-- Start
MyObject:main();

Malgré tout, on finira par arriver à  une situation où la boucle n'est pas tuée, mais la fonction Net.FHttp() renvoie les valeurs errorCode=<vide> et errorCode=2. C'est en tout cas ce que j'ai constaté sur le module Update Notifier, et sur mes propres essais. Donc il semblerait qu'encore une fois, bien que la variable Web soit située dans la fonction main(), la mémoire ne soit pas correctement libérée.

 

 

Pour finir, en 3ème étape je pense avoir trouvé une solution stable, qui tourne depuis une 15zaine de jours chez moi, avec un passage dans la boucle toutes les 60 secondes, donc assez intensif.

L'idée est de définir la variable Web une seule fois, et non plus dans le code de la fonction :

--------------------------------------------------
-- Exemple de Main loop
--------------------------------------------------

if (MyObject == nil) then MyObject = {
	-- System variables
	Web = Net.FHttp(fibaro:get(fibaro:getSelfId(), 'IPAddress'), 80),
	-- Main code
	main = function(self)
		local payload = "/url/a/appeler?avec&des&parametres"
		response, status, errorCode = self.Web:GET(payload)
		if tonumber(status) == 200 and tonumber(errorCode) == 0 then
			if (response ~= nil) then
				-- Actions à  effectuer
				-- ...
			else
				fibaro:debug('Error : Can not connect to Web server, empty response')
			end
		else
			fibaro:debug('Error : Can not connect to Web server, status='..status..', errorCode='..errorCode)
		end
		-- Wait 60s
		fibaro:sleep(60*1000)
	end
}
fibaro:debug("Function successfully loaded in memory")
end

-- Start
MyObject:main();

Si les experts ont un avis sur la question, je suis preneur.

En attendant, j'espère que ça pourra servir.

 

Dans tous les cas, j'espère que la v4 résoudra ces problèmes.

 

  • Upvote 1

Partager ce message


Lien à poster
Partager sur d’autres sites

Désolé de te contredire, ici ce n'est pas un problème de mémoire mais un bug au niveau de la la bibliothèque json introduite par Fibaro depuis la V3.5xx. ;)

  • Upvote 1

Partager ce message


Lien à poster
Partager sur d’autres sites

euh.... je veux bien, mais alors pourquoi la fonction Web:GET() renvoie un statut vide, alors même qu'on n'a pas encore fait un appel au Json pour parser le résultat ?

Partager ce message


Lien à poster
Partager sur d’autres sites

Oui en effet c'est un bug de la bibliothèque json, avec xbmc elle plante entre 1 à15 minutes donc j'ai du tricher et passé par un virtual device.

Le problème est une fois qu'elle choppe un mauvais retour du json elle reste dessus et la seule solution est de réenregistrez pour tout réinitialiser.

Partager ce message


Lien à poster
Partager sur d’autres sites

ah ok.

 

En tout cas, dans mon dernier exemple de script, ça fonctionne bien comme ça chez moi, bug JSON ou pas.

Je n'ai pas d'explication alors...

Partager ce message


Lien à poster
Partager sur d’autres sites

Oui pas con :rolleyes: et dans ce cas quel est le code d'erreur ?

 

Mais je suis perplexe concernant ton explication sur la gestion des ressources car

 

1 - Dans l'exemple de l'UpdateNotifier, justement l'objet Net.FHttp est crée a chaque passage de boucle et n'est pas conservé en mémoire, la variable locale étant détruite...

2 - Mes vd Freebox et SONOS sont des petits scripts de 2500 ou 3000 lignes de code (oui j'ai pas encore tout montré ;)) et je n'ai pas de problèmes et pourtant il y a matière a optimiser hum -_- ...

 

PS: Le fait est que j'ai résolu tout mes problèmes avec Net.FHttp, je ne l'utilise plus :lol:

Partager ce message


Lien à poster
Partager sur d’autres sites

Avec mon 3ème exemple de script, justement je n'ai pas de code d'erreur. Je veux dire que j'ai toujours le retour (status==200 and errorCode==0), et ne rentre jamais dans le cas de figure else.

 

Pour mon explication :

1 - j'ai supposé que les ressources ne sont pas correctement libérées, même si les variables locales sont déclarées dans la fonction. En fait, j'insinue que les variables locales ne sont pas détruites. Ou plutôt un gabage collector qui ne fait pas son boulot correctement. Après je peux me tromper, je ne suis pas développeur, j'ai juste quelques notions...

2 - Ton Update Notifier utilise Net.FHttp et je l'ai vu tomber dans le cas que je mentionne dans mon 2nd exemple de script.

 

Oui ton LUA Framework est une solution, mais j'essaie de faire avec les fonctions de bases :D

Partager ce message


Lien à poster
Partager sur d’autres sites

Au début je pensais comme toi et j'avais testé pour voir si les variables locales étaient bien détruites, la réponse est oui (valeurs & objets).

 

Après en LUA tu peux appeler manuellement le garbage collector cela peut aider

collectgarbage("collect");

:)

  • Upvote 1

Partager ce message


Lien à poster
Partager sur d’autres sites

Ah intéressant cette fonction, merci :)

Partager ce message


Lien à poster
Partager sur d’autres sites

Celle la je la connaissais pas non plus.

Partager ce message


Lien à poster
Partager sur d’autres sites

Et ça sert àquoi concrètement "collectGarbage" ?

Partager ce message


Lien à poster
Partager sur d’autres sites

Vider la poubelle

Tous les objets détruit sont maintenu dans une poubelle qui se vide automatiquement. Et tu peux forcer cela.

Je ne la connaissais pas non plus cette fonction... Merci.

Partager ce message


Lien à poster
Partager sur d’autres sites

Une de plus que j'utilise dans mon framework lua :

  -- getCurrentMemoryUsed()
  -- return total current memory in use by lua interpreter
  getCurrentMemoryUsed = (function()
    return collectgarbage("count");
  end)

collectgarbage("count") me permet de vérifier notamment la "gestion de la mémoire" dans mes libs. Attention Le Mainloop et les boutons sont des Sandbox (bacs àsable) , le collectgarbage("count") retourne uniquement la mémoire utilisée par le script dans le sandbox.

Partager ce message


Lien à poster
Partager sur d’autres sites

Donc la mémoire total d'un module virtuel égal (environ) :  collectgarbage("count") de chaque bouton + celui du Main Loop ?

Partager ce message


Lien à poster
Partager sur d’autres sites

Question bête, existe-t-il un moyen d'accéder à  un objet définit dans la MainLoop depuis le code LUA d'un bouton ?

J'aimerais pouvoir définir des fonctions dans la MainLoop et pouvoir les appeler depuis des boutons plutôt que de faire de la duplication de code.

J'imagine que ce n'est pas possible mais dans ce cas, quels sont les "Best Pratices" pour ça et pour limiter au maximum les duplications de code ? 

Partager ce message


Lien à poster
Partager sur d’autres sites

Malheureusement cela n'est pas possible.

 

Vu que chacun (main loop, bouton, ...) travail de manière étanche, il n'y a pas d'autre moyen que de dupliquer le code ou de jouer sur des variables globales pour piloter le tout. Exemple le métier se trouve dans le main loop en attente qu'un variable globale change d'état (pulling) ou une scène (pour profiter des événements). Ainsi le code métier est uniquement à  un endroit et les boutons ne pilotent que des variables globales.

 

Je suis pas très clair ... désolé.

Partager ce message


Lien à poster
Partager sur d’autres sites

Merci Steven.

Sisi, c'est très clair et j'ai pensé àce type d'architecture mais comment traiter les cas où tu as besoin de passer un paramètre de function et surtout besoin d'un retour ?

Partager ce message


Lien à poster
Partager sur d’autres sites

T'es obligé de passer par une/des variable(s) globale(s) je pense pour assurer la communication entre les différentes boutons d'un module virtuel.... pas très propre tout ça :/

Partager ce message


Lien à poster
Partager sur d’autres sites

Oui et ça peut vite devenir une usine àGaz !

Parce que quand tu as plusieurs boutons qui peuvent appeler la même fonction, ça peut rapidement partir en vrille.

Sinon la valeur d'un label c'est peut-être jouable aussi ?

Partager ce message


Lien à poster
Partager sur d’autres sites

Oui exacte, il me semble avoir déjàvu un module virtuel sur le forum qui utilisait la valeur d'un label, mais bon c'est les mêmes limitations que les variables globales en fait.

Partager ce message


Lien à poster
Partager sur d’autres sites

Je le fais sur mon module virtuel de Chauffage (cf ma signature). L'appui sur un bouton (+1, +0.5,...) va chercher une valeur visible d'un label, faire un calcul puis modifier la valeur du label. Les labels sont utilisable comme des variables globales.

Partager ce message


Lien à poster
Partager sur d’autres sites

Merci Steven, joli VD, je viens de jeter un oeil.

En fait ce que je cherche à  faire, c'est de créer un VD qui rassemblerait tout un ensemble de fonctions que j'utilise couramment.

Un genre de "HelperDevice".

Le code de chaque fonction serait défini derrière un bouton dédié, associé à  2 labels : un pour les éventuels paramètres, l'autre pour le retour de la fonction.

Ces labels pourraient par exemple contenir du JSON

 

Le souci que je vois venir c'est le cas du "multithreading" : si la même fonction est appelée dans la même période de temps par 2 modules et/ou scènes, bah ça va partir en vrille.

Au final ça fait une usine à  gaz pour un résultat pas satisfaisant.

 

Je réfléchi à  ça depuis quelques jours de mon côté car je déteste la duplication de code et ça me gonfle que la HC2 ne propose pas un genre de "Repository" dans lequel on pourrait déposer des scripts persos qu'on pourrait ensuite simplement importer au début des scènes ou des virtual device.

 

Qu'en pensez-vous ?

Est-ce que quelqu'un a déjà  essayé de formuler cette demande au support fibaro ?

Ils pourraient aussi prévoir un namespace "user" qui permettrait d'appeler des fonctions persos comme le namespace fibaro ? user:myfunction()

 

Merci d'avance pour vos avis.

Partager ce message


Lien à poster
Partager sur d’autres sites

J'ai demandé cela àFibaro àplusieurs reprise depuis la sortie du HC2 que j'utilise depuis la version 1.xx et honnêtement ils ne sont pas prêt àapporter une réponse positive malheureusement, il faudrait déjàcommencer par ne pas brider LUA: _G, loadstring, etc.. ;).

Partager ce message


Lien à poster
Partager sur d’autres sites

Arf, dans ce cas il faut hacker la box et débrider les bonnes fonctions nous-même  :)

Ca doit certainement être faisable.

Partager ce message


Lien à poster
Partager sur d’autres sites

Ce n'est pas une pratique à  conseiller. Une telle manipulation est une violation de la propriété et annule totalement la garantie, mais après tout le monde fait qu'il veut en privé...  Plus sérieusement, le jour ou je pense à  hacker (pour de vrai) ma box alors c'est qu'il est tant d'en changer ;)

Partager ce message


Lien à poster
Partager sur d’autres sites

×