Aller au contenu
jjacques68

QA Réveil - conseils

Recommended Posts

Non la table et la classe sont 2 choses bien distinctes

C'est ce que j'ai essayé de te dire plus haut.


La table, c'est facile à comprendre, c'est un mécanisme de base en LUA (et même global en LUA car tout, sans exception, est rangé dans des tables).

Cela permet de "ranger" les fonctions qui vont ensemble, avec quelques variables de différents types (string, number, etc... ou encore des tables contenant d'autres choses).

Une pseudo programmation orientée objet.

 

Le mécanisme de "class", c'est de la vraie programmation orientée objet.

Une classe est une représentation abstraite, qui décrit un concept, lequel sera ensuite instancié en objets distincts.

Ce qui est intéressant, ce qu'une classe peut "hériter" d'une autre, et cela à l'infini.

 

Prenons un exemple :

Je crée une classe "être vivant" dans lequel je décrit le mécanisme des cellules, etc.

Ensuite on a une classe "mammifères" qui hérite des propriétés de la classe parent "être vivant". Inutile de redécrire les cellules, cela a déjà été fait. Mais on va y ajouter la description du mode de reproduction, avec différentes fonctions comme accoucher() etc.

Ensuite, on crée une classe "humain", avec des fonctions comme marcher(), etc.

Puis 2 classes "homme" et "femme".

Voilà, on a tout ce qu'il faut.

Maintenant on va pouvoir instancier environ 3.5 milliards d'"objets" hommes, et autant de femmes objets (désolé elle était trop facile :lol:.... pas taper)

Chaque objet aura des attributs (variables) qui lui seront propre : l'age, la couleur des cheveux, etc.

Inutile de redéfinir les fonctions, cela a été fait au niveau des classes.

 

Appliqué à la HC3, le principe de la programmation objet est utilisé pour décrire les modules, desquels on dérive les différents types et sous-types de modules (sensor => binary sensor => smoke sensor), mais également les QuickApps... et leurs enfants !

C'est pour cela que lorsqu'on veut utiliser des child devices, on doit créer une classe (on lui donne le nom qu'on veut.... MyClass dans l'exemple de Fibaro), et on précise de quelle classe parent elle hérite : QuickAppChild. Sachant qu'elle hérite elle-même de la classe QuickApp.

 

Cela explique pourquoi dans un child on peut appeler des fonctions comme self:debug() puisque la fonction a été héritée de QuickAppChild qui a elle-même été héritée de QuickApp, qui l'a elle-même été héritée de ce qu'il y a au dessus (on ne le sais pas précisément, même si on peut le deviner en cherchant)

On n'a jamais eu besoin de déclarer cette fonction self:debug(), pourtant on peut l'utiliser aussi bien dans notre QuickApp parent que nos QuickAppChild enfants.

Mais on peu aussi décider de la redéclarer pour en modifier son comportement, c'est tout à fait possible... on parle alors de "surcharge".

 

 

Revenons à notre code utilisateur personnel dans le QuickApp.

A priori, pour 99% de nos codes LUA, on a aucun intérêt à utiliser le mécanisme de classes proposé par Fibaro.

Pour la simple raison qu'on n'a pas besoin d'instancier plusieurs objets.

Si je reprend mon onduleur, j'ai besoin d'un seul objet SNMP, donc aucun besoin d'en faire une classe instanciée en plusieurs objets distincts.

Idem pour Surveillance Station, Kodi, etc.

Là où on a besoin d'instancier des objets distincts, c'est pour les modules enfants... ça tombe bien, on utilise justement QuickAppChild comme imposé par Fibaro.

 

Je me suis permis d'utiliser le mécanisme de class de Fibaro pour GEA car j'avais besoin d'instancier 2 objets distincts : l'instance qui tourne en permanence, et l'instance instantanée déclenchée sur trigger. C'est une autre façon (complètement différente) de gérer le multi-instance comme on l'avait pour les scènes sur la HC2. Et je dis bien complètement différente, ça n'a absolument rien à voir sur plein d'aspects.

 

Mais attention, ce mécanisme de class n'est pas du LUA standard, c'est une implémentation spécifique de Fibaro.
D'ailleurs quand tu fais un type(...) d'une classe ou d'un objet instancié depuis une classe, on n'obtient pas "table" mais "userdata". Car c'est un objet propriétaire, non standard LUA, qui a été codé en langage C par Fibaro.

Petit inconvénient, on ne peut pas parcourir la classe ou l'objet avec une boucle for k,v in pairs(...) comme on le ferait pour une table, impossible donc de découvrir ce qu'il contient.

Pour finir, ça rend le code moins portable (qui sait comment sera faite la HC4).

Comme je le disais hier, j'étais bien content avec SNMP, j'ai pu porter le code en un temps record car c'était du LUA standard (sauf l'appel aux fonctions spécifiques UDPSocket)

Et je dois aussi dire que malgré l'immense complexité de GEA, son organisation structurée m'a permis de le porter sans grandes difficultés, là où tout le monde pensait qu'il serait impossible de faire tourner GEA sur HC3. Bon OK ça n'a pas été simple pour autant....

 

Bref, je ne te conseille pas l'usage des classes dans tes codes LUA en dehors de l'utilisation qui nous intéresse : la gestion des QA enfants.

 

Il y a 12 heures, jjacques68 a dit :

D'ailleurs visiblement, on est pas obligé d'utilisé une class ou un tableau de fonctions dans un fichier !

On peut simplement y déplacer des fonctions du main ! 

Ce que j'ai fait pour le code des actions sur les boutons. Histoire de pas polluer le code dans le main... 

Personnellement, je n'approuve pas.

Dans main, je préfère y laisser tout ce qui a trait au QuickApp, donc typiquement la gestion des boutons, sliders, le onInit(), et la boucle principale (celle qui est relancée à intervalle régulier avec settimeout())

 

Et comme je le décrivais précédemment, on utilise les fichiers pour y ranger le reste : les outils génériques, les outils spécifiques (gestion de l'API du NAS, de Kodi, de l'IPX800, du protocole SNMP, etc)...

 

Et dans chaque fichier, on y trouve une table (assimilable à un objet, même si comme je viens de l'expliquer c'est un faux objet) qui permet de regrouper les fonctions et les variables locales.

Mécanisme simple et efficace : un objet = un fichier. Je trouve cela simple à maintenir, et la réutilisation partiel du code dans un autre QA est simplifiée : il suffit de reprendre tout le contenu d'un fichier (je pense à mes "tools" notamment)

 

Mais c'est juste ma façon de voir les choses, je ne suis pas programmeur de métier.

 

Il y a 12 heures, jjacques68 a dit :

@jang : I have read your link more and more. I don't understand anymore with the "shared" :) sorry, it's too strange for me :) 

En résumé il propose un mécanisme pour charger des sous-modules dans les différents fichiers attachés au QA, permettant de gérer les situations qui pourraient se produire dans certains gros QuickApp communautaire développés par plusieurs personnes, dans lesquels on pourrait potentiellement avoir plusieurs librairies portant le même nom.
En ce qui me concerne, je pense notamment à ma fameuse librairie tools donc le nom est tellement générique qu'il a également été utilisé par Steven dans GEA. Du coup se retrouver avec 2 tables appelées tools dans le même QuickApp, ça coince.

 

La solution de jang permet de charger une librairie avec un nom personnalisé utilisable dans le reste du QuickApp.

 

Mais j'avoue que c'est assez avancé, j'ai lu ça hier soir et j'ai eu un peu de mal à comprendre, surtout arrivé vers la fin.

Et je trouve relativement lourd à l'usage.... parce que je n'ai pas l'habitude de cette manière de coder.

Je ne sais pas si je vais l'adopter... à suivre

Partager ce message


Lien à poster
Partager sur d’autres sites

oh punaise ! il va falloir que je lise ton explication plusieurs fois !!

 

merci pour ta patience !!

Partager ce message


Lien à poster
Partager sur d’autres sites

J'ai bien lu tes explications.

Je comprends la notion de class et d'héritage, d'instanciation, ...

Mais pas évident de faire la parallèle sur la HC3 !

avec ce que du dis et ce que j'imagine, je dirais (on va voir si j'ai compris qqch :))

 

1ère couche on a une class principale "Fibaro" (qui  inclut des "composants" comme math, json, string, fonction spécifiques à Fibaro, ...)

2ème couche : une class "QuickApp" qui hérite de "Fibaro" (avec des fonctions, variables propres au QA en plus)

qu'on utilise quand on créé un QA simple

3ème couche une class "QuickAppChild" qui hérite de "QuickApp"

qu'on utilise pour les QA avec Child ou plutôt pour les Child des QA

 

On pourrait pas créer une autre class QuickAppChild ou encore QuickApp ?

En les appelant autrement biensûr...

(j'en vois pas l'intérêt, mais ce serait possible ?)

 

et ça me fait rebondir sur ton QA qui permet de lister les variables, fonctions et objects disponibles.

Ce sont des éléments de la class QuickApp alors ?

Les variables sont les attributs, les fonctions sont les méthodes ?

 

où on est encore une couche plus haut ?

vu qu'on retrouve des similitudes avec les scènes

 

d'ailleurs les scènes se placent où dans tout ça ?

 

merci d'avance !!

Partager ce message


Lien à poster
Partager sur d’autres sites
Le 24/12/2020 à 18:04, jjacques68 a dit :

1ère couche on a une class principale "Fibaro" (qui  inclut des "composants" comme math, json, string, fonction spécifiques à Fibaro, ...) 

2ème couche : une class "QuickApp" qui hérite de "Fibaro" 

Pas tout à fait

math, json, etc... font partie du LUA natif

fibaro est un objet qui porte des fonctions spécifiques à la HC3 : fibaro.call(), fibaro.debug(), etc...

 

Voir l'exploration des objets du topic :

Tu y retrouveras Device, QuickApp, QuickAppBase, QuickAppChild ... qui sont des classes

Element très intéressant, tu verras " quickApp ", qui est l'objet représentant notre QuickApp en cours, instancié à partir de la classe QuickApp (différence de majuscule)

 

Le 24/12/2020 à 18:04, jjacques68 a dit :

On pourrait pas créer une autre class QuickAppChild ou encore QuickApp ? 

En les appelant autrement biensûr... 

(j'en vois pas l'intérêt, mais ce serait possible ?)

Justement, c'est exactement ce qu'on fait quand on crée des QA enfants : on définie notre propre classe qui hérite de QuickAppChild :

class 'MyChild' (QuickAppChild)

Ce qui nous permet d'ajouter nos propres fonctions personnalisées.

Chaque objet child instancié depuis notre classe personnalisée, est stocké dans le tableau self.childDevices

 

 

Le 24/12/2020 à 18:04, jjacques68 a dit :

d'ailleurs les scènes se placent où dans tout ça ?

nul part.... elles ont leur propre moteur LUA qui est plus limité, il embarque moins de fonctions.

Je ne suis même pas certain que le support des Classes existe dans les scènes, il faudrait tester pour le sport (mais quel intérêt ?)

 

 

Partager ce message


Lien à poster
Partager sur d’autres sites

merci pour ces clarifications !

ça s'éclaircit :) 

j'aurai sans doutes d'autres questions :) 

Modifié par jjacques68

Partager ce message


Lien à poster
Partager sur d’autres sites

Je reviens dans le sujet tu topic avec un problème... très très très très étrange, limite délirant du comportement des 2 QA réveil :

 

J'ai l'impression que les 2 child se marchent dessus, comme s'ils n'étaient pas si indépendant que ça !!!

J'espère que c'est moi qui fait mal qqch, parce que sinon ça remet en cause le principe des QA Parent/Child !!

 

Je m'explique : 

 

Dans la class "WAKEUP" de ces QA, j'ai une méthode "wakeStart",  sorte de handler, qui me permet de gérer un allumage progressif de mes lumières.

 

le voici simplifié et raccourci : 

 

"idLight" est une variable propre au Child qui stocke l'ID du dimmer qu'il doit gérer.

désolé c'est pas de tout repos de devoir lire ça...

--[[--------------------------------------------------
WAKEUP : wakeStart
----------------------------------------------------]]
function WAKEUP:wakeStart()

    local idLight = self:getVariable("idLight")
    local handlers = {

        -- Début du cycle de réveil--------------------------------------------
        start = function(arg)
            self:debug(self.id, "start")
            post({next='incValue',setValue=1})
        end,

        -- boucle Allumage progressif -------------------------------------------
        incValue = function(arg)
            --sort du cycle d'allmuage si lumière > 98 %
            if fibaro.getValue(idLight, "value") > 98 then 
                post({next='wait'})
            else
                self:debug(self.id, arg.setValue, "%")
                fibaro.call(idLight, "setValue", arg.setValue)
                post({next='incValue',setValue=(arg.setValue+1)},9*1000)
            end
        end, 
    
    	[...]
    }
  
    --[[---------------------------------
    -----------------------------------]]
    --fonction appelante
    function post(arg, time)
        fibaro.setTimeout(time or 0, function() handlers[arg.next](arg) end)
    end

    --déclenche le cycle
    post({next='start'})
  
 end

 

Alors ça fonctionne super bien si UNE SEULE LUMIERE est actionnée. Donc un seul QA (donc un seul réveil)

Si je mets les 2 lumières, ça se prend les pieds dans le tapis.

Une des 2 ne continue pas son cycle d'allumage.

 

Voici le debug qui rend ça bien visible et pourra peut-être aider à comprendre

image.png.01ba6d6c07d084ee6346d26dd0c326d6.png

 

J'ai donc appelé la méthode "wakeStart" des 2 QA à 12h30.

731 = l'ID du child pour le réveil "de gauche"

732 = l'ID du child pour le réveil "de droite"

On voit bien qu'ils ont bien démarré !

que la graduation des dimmer se fait bien à la première boucle de la fonction "incValue" !

et c'est après que ça déconne !!!!

 

On a l'impression que les 2 child ont fusionné !

Le QA 731 est comme qui dirait passé sur le QA 732 !

Et le dimmer associé ne progresse plus bien sûr...

 

D'où peut provenir un tel comportement ?

 

C'est délirant ! voir limite énorme !

 

 

Je précise que ma class "WAKEUP" est écrite dans un fichier autre que le main... je sais pas si ça a de l'importance...

 

EDIT : j'ai placé la class dans le main, ça a rien changé au comportement... :( 

 

Modifié par jjacques68

Partager ce message


Lien à poster
Partager sur d’autres sites

Punaise j'ai exactement le même comportement avec mes QA Child des caméras pour la gestion de la patrouille.

Même principe, j'ai un "handler" pour les faire bouger.

Si les 3 patrouillent en même temps, ça se prend les pied dans le tapis.

 

Inquiétant ça ! :unsure:

Modifié par jjacques68

Partager ce message


Lien à poster
Partager sur d’autres sites

je pense que c'est normal.

 

Premier appel de wakeStart() :

tu définies ta variables locale ainsi :

local idLight = self:getVariable("idLight")

 

Qui va prendre la valeur 731, et qu'il va conserver... jusqu'au 2nd appel de wakeStart(), moment où cette même variable locale sera écrasée avec la valeur du 2nd child : 732

Ensuite, il conservera indéfiniment 732

 

 

Il faut te méfier quand tu mélanges des variables locales avec plusieurs instances d'objets, chacun accessibles au travers de leur propriété self.

 

Je n'aurais personnellement pas du tout écrit tes fonctions ainsi, car elles portent à confusion.

 

Dans l'immédiat, tu peux essayer de remplacer la variable local par une variable stockée dans le child :

self.idLight = self.idLight or self:getVariable("idLight")

Déjà c'est bien plus efficace, car on conserve sa valeur tant que le QA n'est pas redémarré, donc on évite des appels inutiles à self:getVariable() (assez lent, puisqu'il va parcourir l'API)

Ensuite, dans tes fonctions, tu utilises self.idLight, tu es certain que cette variable est associée à chaque child, indépendamment des valeurs des variables locales.

 

 

 

Partager ce message


Lien à poster
Partager sur d’autres sites

je me posait la question justement de la porté de ces variables et du fameux "self" dans un QA Child...

Modifié par jjacques68

Partager ce message


Lien à poster
Partager sur d’autres sites
il y a 8 minutes, Lazer a dit :

Il faut te méfier quand tu mélanges des variables locales avec plusieurs instances d'objets, chacun accessibles au travers de leur propriété self.

je pensais avoir bien fait en mettant tout en local dans une fonction, qui devrait être propre à chaque Child...

Partager ce message


Lien à poster
Partager sur d’autres sites

Ah oui exact

mais tu as un souci avec self, au moment où tu appelles les fonctions locales, le self ne correspond plus à chaque child

 

je t'ai dis que je n'aurais pas du tout écrit ces fonctions de cette façon là.... j'aurais simplement une méthode de WAKEUP qui s'appelle elle-même avec settimeout(), de cette façon tu es certain que self pointe bien sur le bon child en cours.

 

Cela dit, il me semble que @jang nous avais dit un jour qu'un child n'est pas censé se mettre à jour tout seul.

Tout cela devrait être supervisé par le parent, dans directement dans une méthode de QuickApp.

A lui de parcourir les children et de les mettre à jour.

C'est en tout cas la technique que j'utilise dans tous mes QA, et je n'ai jamais eu de souci de "mélange" de child.

Partager ce message


Lien à poster
Partager sur d’autres sites

Ben il me semble que c'est que je fais, 

 

la méthode wakupStart() s'auto appelle ? nan ?

 

EDIT : 

 

nan en fait nan, elle ne s'auto appelle pas, ce sont des fonctions locales à l'intérieur de wakupStart qui s'auto appellent...

 

Modifié par jjacques68

Partager ce message


Lien à poster
Partager sur d’autres sites
il y a 49 minutes, Lazer a dit :

mais tu as un souci avec self, au moment où tu appelles les fonctions locales, le self ne correspond plus à chaque child

alors visiblement j'ai résolu le problème.

je n'arrive pas à expliquer comment... tu m'as mis la puce à l'oreille avec les self...

 

J'ai décalé toutes mes variables dans le "__init" du Child.

Je fais appel avec "self.---"

La fonction "post", j'en ai fait une méthode seule, que j'appelle avec "self:post()"

 

et j'ai une bonne indépendance des Child avec ça maintenant.

 

Mais je comprends pas pourquoi, ça m'énerve, c'est le bordel dans ma tête :( 

 

Modifié par jjacques68

Partager ce message


Lien à poster
Partager sur d’autres sites

bon et bien j'ai trouvé ce qui cloche

 

dans une méthode d'un Child, ça ça marche pas :

function MyClass:MyFunc()

    function Func()	
        setTimeout(function() Func() end, 1000)
    end

    Func()
end

 

Je sais qu'on aurait pu aussi faire : 

function MyClass:MyFunc()
    setTimeout(function() self:MyFunc() end, 1000)
end

Par contre ça ça marche.

 

Mais dans mon premier exemple, c'était pratique si on a plusieurs fonctions qui doivent s'enchainer :

function MyClass:MyFunc()

    function Func1()	
        setTimeout(function() Func2() end, 1000)
    end

    function Func2()	
        setTimeout(function() Func3() end, 1000)
    end

    function Func3()	
    end

    Func1()

end

enfin ça fonctionne si 1 QA tourne, si plusieurs, ça se mélange.

Je sais pas pourquoi.

J'ai essayé avec "local function" dans la méthode mais c'est pire.

 

? ? ?

Partager ce message


Lien à poster
Partager sur d’autres sites
Il y a 1 heure, Lazer a dit :

je t'ai dis que je n'aurais pas du tout écrit ces fonctions de cette façon là

en fait après avoir analysé tout ça tu aurai fait un truc comme ça ?

function MyClass:Func1()	
    setTimeout(function() self:Func2() end, 1000)
end

function MyClass:Func2()	
    setTimeout(function() self:Func3() end, 1000)
end

function MyClass:Func3()
	[...]
end

 

Partager ce message


Lien à poster
Partager sur d’autres sites

Oui voilà plus dans l'esprit de ta dernière suggestion.

 

Cela dit, je pense que tu peux simplifier, pourquoi avoir Func1 et Func2, alors que tu pourrais en voir une seule et passer les arguments en paramètre.

Enfin, je veux dire, si Func1 et Func2 font la même chose, alors il faut les regrouper en 1 seul avec un code commun, et des arguments en paramètre.

Si elles font des choses différentes, dans ce cas, tu peux garder des fonctions différentes.

 

Mais je maintiens ce que j'ai dis plus haut.

Il ne devrait pas y avoir de Settimeout dans la classe enfant.

Le settimeout devrait se faire dans une loop du parent (classe QuickApp), qui à chaque passage de boucle appelle une fonction des enfants : MyChild:update() ou un truc dans le genre

 

Je pense qu'une bonne pratique, c'est de laisser l'intelligence et l'orchestration des actions dans le parent. Et ne donner aux enfants que des tâches le plus basiques possible.

Cette bonne pratique éviterai les confusions avec des variables locales je pense.

 

Bref, c'est une toute autre façon d'organiser son code

Partager ce message


Lien à poster
Partager sur d’autres sites
il y a 36 minutes, Lazer a dit :

Enfin, je veux dire, si Func1 et Func2 font la même chose, alors il faut les regrouper en 1 seul avec un code commun

tout à fait, là c'est un exemple, dans mon cas elles sont différentes justement.

 

il y a 36 minutes, Lazer a dit :

Mais je maintiens ce que j'ai dis plus haut.

Il ne devrait pas y avoir de Settimeout dans la classe enfant.

Le settimeout devrait se faire dans une loop du parent (classe QuickApp), qui à chaque passage de boucle appelle une fonction des enfants : MyChild:update() ou un truc dans le genre

ah mais là je te suis pas ! :)

 

Les child devrait pouvoir avoir leurs propres méthodes qui s'exécutent tout à fait indépendamment les uns des autres ainsi que du parent !

 

Mes réveils sont un bon exemple.

le premier déclenche à 7h00 le second peut déclencher à 7h01 si je veux.

Ils ont un cycle d'allumage particulier.

Ils doivent être les 2 autonomes et le parents ne gère rien du tout dans l'histoire.

Ce ne sont que les variables propres aux child qui feront la différence.

Ce serait un vrai casse-tête de passer par le parent... j'imagine pas trop le truc...

 

il y a 36 minutes, Lazer a dit :

Je pense qu'une bonne pratique, c'est de laisser l'intelligence et l'orchestration des actions dans le parent. Et ne donner aux enfants que des tâches le plus basiques possible.

Cette bonne pratique éviterai les confusions avec des variables locales je pense.

ce doit être suivant l'usage, par exemple pour l'IPX, en effet c'est le parent qui a toute l'intelligence (socket)

Les child ne font que utiliser les fonctions du parent (du moins les binarySwitch pour actionner les sorties, les inputs ne fond pas appel au parent, sont complement passif, c'est le parent qui modifie leur état)

 

Punaise avec cette box, il y a mille façons de faire les choses, c'est génial, mais ça rend fou :) 

Chacun voit le truc différemment !

 

on est plus vraiment dans des child tout simple comme on peut le voir souvent (qui affiche une température, un état, ...)

lls deviennent actif. :) 

 

PS : merci de m'avoir mis sur la piste pour mon soucis, ça marche très bien maintenant :60:

Modifié par jjacques68

Partager ce message


Lien à poster
Partager sur d’autres sites

Tu fais comme tu le sens, mais je reste persuadé que toute la logique du réveil est déclenchable depuis le parent.

Bon l'essentiel c'est que tu t'y retrouves dans ton propre code.

Partager ce message


Lien à poster
Partager sur d’autres sites

:) oui le tout est de s'y retrouver...

mais y a toujours cette zone d'ombre quant à la porté des variables déclarées dans un child... et le "self"...

 

Partager ce message


Lien à poster
Partager sur d’autres sites
Il y a 1 heure, jjacques68 a dit :

:) yes it's all about finding your way ...

but there is always this gray area as to the scope of the variables declared in a child ... and the "self" ...

 

 

function QuickApp:test(x) self.x = x; self:debug("self.x is set to ",x) end

is identical to

function QuickApp.test(self,x) self.x = x; self:debug("self.x is set to ",x) end

'self' is inserted as the first variable of the function, before x when declared with ':'

 

calling

self:test(7)

is identical to calling

self.test(self,7)

 

...so when you call Object:test(7)

it is the same as Object.test(Object,7)

so 'self' will be bound to Object

 

When you call Child:Func1()

'self' will be bound to Child, because the call is identical to Child.Func1(Child)

It means that inside Func1 we have access to 'self', the object that called Func1

 

It's syntactic sugar for Lua...

Partager ce message


Lien à poster
Partager sur d’autres sites
il y a 23 minutes, jang a dit :

'self' will be bound to Child

ok, I understand.

 

I had problem in a function in a class with variable (I think...).

It seemed to mix with another childs ??

(see the message above, with "WAKEUP" class...)

function myClass:Func1()

    local var1
    local var2

end

when I declare the variables in the "__init" of child, it works fine.

I don't understand why.

Partager ce message


Lien à poster
Partager sur d’autres sites

×