Message populaire OJC Posté(e) le 4 janvier 2018 Message populaire Signaler Posté(e) le 4 janvier 2018 (modifié) VD Yeelight Controller v. 1.3.1 - 18/11/2019 Je vous présente le VD Yeelight Manager qui, comme son nom l'indique, permet d'intégrer les produits Yeelight avec la HC2. Pour être très précis, je ne dispose que d'une Bedside Lamp et je n'ai donc pas testé ce VD avec d'autres produits. Cependant, l'API étant générique, il n'y a pas de raisons que ça ne fonctionne pas aussi. Seule limite, je n'ai pas intégré dans le VD la gestion de la background light puisque la Bedside Lamp n'en a pas. Trêve de bavardages, voici l'usine à gaz : VD Yeelight Controller v. 1.3.1 Paramétrage des plus classiques, il vous suffit de renseigner l'IP de la Yeelight dans le champ IP du VD, et le port dans le champ Port du VD (c'est le port 55443 qui est utilisé par les Yeelights). Avant d'importer le VD, je vous conseille de l'ouvrir dans un éditeur de texte et de faire un rechercher/remplacer pour définir l'ID de l'icône des boutons (rechercher la chaîne "buttonIcon":1011). IMPORTANT = Il faut activer le Contrôle sur le réseau local depuis l'application Yeelight, pour chaque lampe. Pour pouvoir gérer une Yeelight 'presque' comme un module, il suffit d'insérer dans la scène ou le code du bouton ou du Main Loop le code suivant : _y={credits='Yeelight Controller v. 1.3.0 - Copyright 2018 Olivier Meyer - GNU GPLv3',global='y_yeelight_global',item='y_yeelight_',props={power=true,bright=true,ct=true,rgb=true,hue=true,sat=true,color_mode=true,flowing=true,delayoff=true,flow_params=true,music_on=true,name=true,bg_power=true,bg_flowing=true,bg_flow_params=true,bg_ct=true,bg_lmode=true,bg_bright=true,bg_rgb=true,bg_hue=true,bg_sat=true,nl_br=true},f=fibaro,debug=function(s,m,c)s.f:debug(string.format('<span style="color:%s;">%s</span>', c, m))end,here=function(s)local h=type(s.f.getSelfId)=='nil' if h then return h,__fibaroSceneId else return h,s.f:getSelfId(),_elementID_ end end,log=function(s)local h=s:here()if h then local a=s.f:args()if a then if a[1].yeelight then s:debug(a[1].debug,a[1].color);s:debug(a[1].command,a[1].color);os.exit()end end end end,getBtn=function(s,i,n)local c,r=0,api.get('/devices/'..tostring(i))['properties']['rows'] local x=#r for a=1,x do local y=#r[a].elements for b=1,y do c=c+1 if n==r[a].elements[b].name then return c end end end s:debug('[Yeelight] Unable to locate button '..n..', check virtual device '..i,'Tomato');return nil end,build=function(s,m,p)local c='{"id":1, "method":"'..m..'", "params":['for i=1,#p do if type(p[i])=="number"then c=c..p[i]else c=c..'"'..p[i]..'"'end;if i~=#p then c=c..', 'end end;c=c..']}\r\n'return c end,load=function(s,v)local g=s.f:getGlobalValue(v)if string.len(g or '')>0 then local d=json.decode(g)if d and type(d)=='table'then return d else s:debug('[Yeelight] Unable to process data, check variable','Tomato')end else s:debug('[Yeelight] No data found!','Tomato')end return nil end,set=function(s,i,d)local g=s:load(s.global)if g[tostring(i)]then local a,b,c=s:here();g[tostring(i)].scene=a;g[tostring(i)].id=b;g[tostring(i)].button=c;g[tostring(i)].command=d;s.f:setGlobal(s.global,json.encode(g))end end,call=function(s,i,m,p)local b=s:getBtn(i,'btnTransmission')if b==nil then return nil end;local c=s:build(m,p)s:set(i,c)s.f:call(i,'pressButton',b)end,getValue=function(s,i,p,g)if s:checkP(p) then if type(g)~='table' then g=s:load(s.item..i)end if g.properties then if g.properties[p] then if g.properties[p].value then return g.properties[p].value end end end s:debug('[Yeelight] Unable to get value of '..tostring(p)..', please check variable.','Tomato');end return nil end,getModificationTime=function(s,i,p,g)if s:checkP(p) then if type(g)~='table' then g=s:load(s.item..i)end if g.properties then if g.properties[p] then if g.properties[p].modificationTime then return g.properties[p].modificationTime end end end s:debug('[Yeelight] Unable to get modification time of '..tostring(p)..', please check variable.','Tomato');end return nil end,get=function(s,i,p)local g=s:load(s.item..i);return s:getValue(i,p,g),s:getModificationTime(i,p,g)end,getStatus=function(s,i)local g=s:load(s.item..i)if g then if g.status then return g.status end end return nil end,getLastChange=function(s,i)local g=s:load(s.item..i)if g then if g.last then return g.last end end return nil end,checkP=function(s,p)if not s.props[p] then s:debug('[Yeelight] '..p..' is not an existing property!','Tomato') return false end return true end} _y:log() Ce code ci-dessus contient les fonctions suivantes : _y:call(id, method, params) qui permet d'envoyer une commande à la lampe. Les différents boutons du VD permettent d'avoir un aperçu du fonctionnement. Pour aller plus loin, je vous renvoie à la documentation de l'API Yeelight : Yeelight_Inter-Operation_Spec.pdf _y:get(id, property) qui fait exactement la même chose que fibaro:get() _y:getValue(id, property) qui fait exactement la même chose que fibaro:getValue() _y:getModificationTime(id, property) qui fait exactement la même chose que fibaro:getModificationTime() _y:getStatus(id) qui retourne "online" si la lampe est connectée au réseau wifi, et "offline" dans le cas contraire _y:getLastChange(id) qui retourne une table contenant les propriétés qui viennent d'être modifiées et leur nouvelle valeur Les logs générés par l'utilisation de la fonction call() s'affichent dans la zone de debug de la scène (ne pas oublier pour cela la ligne _y:log() et d'autoriser suffisamment d'instances) ou dans celle du bouton / main loop du module virtuel d'où a été utilisé la fonction. Pour pouvoir utiliser ces lampes comme déclencheurs de scène, le VD crée automatiquement une variable globale y_yeelight_XXX, où XXX est l'ID du VD. Cette variable globale contient toutes les propriétés de la lampe et est mise à jour en temps réel. Par exemple, pour lancer l'exécution d'une scène à l'allumage d'une Yeelight : --[[ %% globals y_yeelight_XXX --]] _y={credits='Yeelight Controller v. 1.3.0 - Copyright 2018 Olivier Meyer - GNU GPLv3',global='y_yeelight_global',item='y_yeelight_',props={power=true,bright=true,ct=true,rgb=true,hue=true,sat=true,color_mode=true,flowing=true,delayoff=true,flow_params=true,music_on=true,name=true,bg_power=true,bg_flowing=true,bg_flow_params=true,bg_ct=true,bg_lmode=true,bg_bright=true,bg_rgb=true,bg_hue=true,bg_sat=true,nl_br=true},f=fibaro,debug=function(s,m,c)s.f:debug(string.format('<span style="color:%s;">%s</span>', c, m))end,here=function(s)local h=type(s.f.getSelfId)=='nil' if h then return h,__fibaroSceneId else return h,s.f:getSelfId(),_elementID_ end end,log=function(s)local h=s:here()if h then local a=s.f:args()if a then if a[1].yeelight then s:debug(a[1].debug,a[1].color);s:debug(a[1].command,a[1].color);os.exit()end end end end,getBtn=function(s,i,n)local c,r=0,api.get('/devices/'..tostring(i))['properties']['rows'] local x=#r for a=1,x do local y=#r[a].elements for b=1,y do c=c+1 if n==r[a].elements[b].name then return c end end end s:debug('[Yeelight] Unable to locate button '..n..', check virtual device '..i,'Tomato');return nil end,build=function(s,m,p)local c='{"id":1, "method":"'..m..'", "params":['for i=1,#p do if type(p[i])=="number"then c=c..p[i]else c=c..'"'..p[i]..'"'end;if i~=#p then c=c..', 'end end;c=c..']}\r\n'return c end,load=function(s,v)local g=s.f:getGlobalValue(v)if string.len(g or '')>0 then local d=json.decode(g)if d and type(d)=='table'then return d else s:debug('[Yeelight] Unable to process data, check variable','Tomato')end else s:debug('[Yeelight] No data found!','Tomato')end return nil end,set=function(s,i,d)local g=s:load(s.global)if g[tostring(i)]then local a,b,c=s:here();g[tostring(i)].scene=a;g[tostring(i)].id=b;g[tostring(i)].button=c;g[tostring(i)].command=d;s.f:setGlobal(s.global,json.encode(g))end end,call=function(s,i,m,p)local b=s:getBtn(i,'btnTransmission')if b==nil then return nil end;local c=s:build(m,p)s:set(i,c)s.f:call(i,'pressButton',b)end,getValue=function(s,i,p,g)if s:checkP(p) then if type(g)~='table' then g=s:load(s.item..i)end if g.properties then if g.properties[p] then if g.properties[p].value then return g.properties[p].value end end end s:debug('[Yeelight] Unable to get value of '..tostring(p)..', please check variable.','Tomato');end return nil end,getModificationTime=function(s,i,p,g)if s:checkP(p) then if type(g)~='table' then g=s:load(s.item..i)end if g.properties then if g.properties[p] then if g.properties[p].modificationTime then return g.properties[p].modificationTime end end end s:debug('[Yeelight] Unable to get modification time of '..tostring(p)..', please check variable.','Tomato');end return nil end,get=function(s,i,p)local g=s:load(s.item..i);return s:getValue(i,p,g),s:getModificationTime(i,p,g)end,getStatus=function(s,i)local g=s:load(s.item..i)if g then if g.status then return g.status end end return nil end,getLastChange=function(s,i)local g=s:load(s.item..i)if g then if g.last then return g.last end end return nil end,checkP=function(s,p)if not s.props[p] then s:debug('[Yeelight] '..p..' is not an existing property!','Tomato') return false end return true end} _y:log() local function run() --Code à exécuter à l''allumage de la Yeelight end local trigger = fibaro:getSourceTrigger() if trigger.type == "global" then if trigger.name == "y_yeelight_XXX" then if _y:getLastChange(XXX)["power"] == "on" then run() end end end Et voici les icônes que j'utilise pour l'état on/off de la lampe, celle qui est sur chaque bouton pour signifier l'envoi de la commande, et celle qui indique que la lampe est hors ligne : REMERCIEMENTSun grand merci à @ADN182 qui m'a fourni le VD qu'il avait commencé à développer et qui m'a fait gagner beaucoup de temps dans la compréhension de la manière d'envoyer les commandes à la lampe, vu que l'objet Net.FTcpSocket ne semble documenté nulle part (ou alors je n'ai pas trouvé ). Egalement, je dois pas mal à @Krikroff et à son VD Sonos Remote puisque c'est de là que vient l'idée d'utiliser une variable globale pour faire passer les commandes depuis n'importe quelle scène jusqu'au VD pour qu'il l'envoie à la lampe. De même pour le principe du ping que j'utilise pour détecter quand la lampe est déconnectée du réseau wifi, de manière à relancer automatiquement la connexion TCP lorsqu'elle revient en ligne, ce qui était le souci de @ADN182. Et, enfin, un grand merci également à @Talwayseb pour son VD Philips Hue qui m'a grandement inspiré pour l'esthétique du VD Yeelight Manager. Et aussi à @pepite et @Lazer pour leurs tuyaux pour faire passer les debug entre un VD et un autre VD (_elementID_ !!) ou une scène (fibaro:args()). Modifié le 18 novembre 2019 par OJC Publication de la v. 1.3.1 7 1 2
OJC Posté(e) le 5 janvier 2018 Auteur Signaler Posté(e) le 5 janvier 2018 Petite mise à jour avec notamment l'ajout du ModificationTime pour chaque propriété de la lampe et l'envoi des logs dans la zone de debug de la scène ou du VD d'où a été envoyée la commande.
pepite Posté(e) le 5 janvier 2018 Signaler Posté(e) le 5 janvier 2018 Well done, toujours et encore ;-) Petit ce VD tu vois tout sur ton smartphone ;-)
OJC Posté(e) le 5 janvier 2018 Auteur Signaler Posté(e) le 5 janvier 2018 (modifié) Dernière petite modification : il est désormais possible de modifier le VD à loisir (ajouter ou supprimer des boutons, des étiquettes...) en fonction des besoins et de la lampe (par exemple, l'ampoule blanche n'a pas besoin de tout ce qui concerne la couleur), et ce sans avoir à s'inquiéter du changement de numérotation des boutons Connexion TCP et Transmission . Pour ceux que ça intéresse, voici la fonction qui permet au VD de se débrouiller tout seul pour savoir où appuyer (fonction générique pouvant être reprise telle qu'elle dans n'importe quel VD ou scène pourvu qu'on ait besoin de faire un pressButton) : function getBtn(id, name) count,rows = 0, api.get("/devices/"..tostring(id))["properties"]["rows"] for a, b in pairs(rows) do for c, d in pairs(b) do if c == "elements" then for e, f in pairs(d) do for g, h in pairs(f) do if g == "name" then count = count + 1 if h == n then return count end end end end end end end fibaro:debug("Unable to locate button "..n..", check virtual device "..id) return nil end Et la version minifiée : getBtn=function(i,n)x,r=0,api.get("/devices/"..tostring(i))["properties"]["rows"];for a,b in pairs(r)do for c,d in pairs(b)do if c == "elements" then for e,f in pairs(d)do for g,h in pairs(f)do if g=="name" then x=x+1 if h==n then return x end end end end end end end fibaro:debug("Unable to locate button "..n..", check virtual device "..i);return nil end Modifié le 6 janvier 2018 par OJC 1
Lazer Posté(e) le 6 janvier 2018 Signaler Posté(e) le 6 janvier 2018 Si je peux me permettre, par souci d'optimisation de la performance du code, il faut éviter l'utilisation de la fonction "pairs" qui est très lente. Elle a l'avantage de simplifier l'écriture et la compréhension, mais elle est peu performante, On trouve des benchmarks sur le net. Donc il vaut mieux privilégier l'emploi d'une simple boucle "for". Bon après, c'est surtout vrai si on appelle souvent ce code. Si il est peu utilisé, c'est pas ça qui changera les perfs de la box. Merci pour ce bout de code en tout cas 1
OJC Posté(e) le 6 janvier 2018 Auteur Signaler Posté(e) le 6 janvier 2018 (modifié) @Lazer Chef, oui Chef ! getBtn=function(i,n) r = api.get("/devices/"..tostring(i))["properties"]["rows"] for a=1,#r do for b=1,#r[a].elements do if n == r[a].elements[b].name then return a+b end end end return nil end getBtn=function(i,n)r=api.get("/devices/"..tostring(i))["properties"]["rows"] for a=1,#r do for b=1,#r[a].elements do if n == r[a].elements[b].name then return a+b end end end return nil end Modifié le 6 janvier 2018 par OJC
Lazer Posté(e) le 6 janvier 2018 Signaler Posté(e) le 6 janvier 2018 Je peux faire le chieur ? Tu comptes le nombre d'éléments de tes tableaux à chaque passage dans la boucle, tant qu'à optimiser il veut mieux stocker dans une variable locale avant d'entrer dans la boucle.
OJC Posté(e) le 6 janvier 2018 Auteur Signaler Posté(e) le 6 janvier 2018 (modifié) J'ai du mal à saisir ? EDIT = Comme ça ? cf post précédent Modifié le 6 janvier 2018 par OJC
Lazer Posté(e) le 6 janvier 2018 Signaler Posté(e) le 6 janvier 2018 Comme ceci, non testé : getBtn=function(i,n) local c,r = 0, api.get("/devices/"..tostring(i))["properties"]["rows"] local nbr = #r for a=1, nbr do local nbra = #r[a].elements for b=1, nbra do c=c+1 if n == r[a].elements[b].name then return c end end end return nil end J'en ai profité pour déclarer toutes les variables en local, cela évite d'interférer avec les éventuelles variables du reste du script LUA de l'utilisateur qui intègrera ce code. 2
MAM78 Posté(e) le 6 janvier 2018 Signaler Posté(e) le 6 janvier 2018 (modifié) il y a 40 minutes, Lazer a dit : for a=1, nbr do J'ignorais cette subtilité, tu veux dire que la ligne ci-dessus est exécuter autant de fois que la valeur nbr. Je supposais qu'elle n'était exécutée qu'une fois et que c'était uniquement le contenu de la boucle qui était exécuté autant de fois que la valeur nbr. Il y énormément de codes sur le forum qui sont écrit comme ça : for a=1, #r do Modifié le 6 janvier 2018 par MAM78
Lazer Posté(e) le 6 janvier 2018 Signaler Posté(e) le 6 janvier 2018 Oui tout à fait, la ligne for elle-même est interprétée à chaque passage. Bien sur qu'il y a énormément de code écrit ainsi, tout comme il y a énormément de code avec la fonction pair(). Le propre d'un langage de programmation, c'est aussi de nous faciliter la vie avec des raccourcis, mais ces raccourcis sont rarement performants au sens du temps d'exécution machine. Si on pousse plus loin encore le raisonnement, un appel de fonction, c'est très long, il faut placer les paramètres sur la pile, décaler le pointeur d'exécution du programme, etc. D'un point de vue performance, un code linéaire est donc plus performant qu'un code bien structuré avec de multiples fonctions. Sauf qu'à un moment donné, on est quand même obligé d'utiliser les fonctions si on veut que le développeur, un simple humain, puisse s'y retrouver dans la structure du code. De plus, les fonctions permettent de factoriser le code, donc de minimiser l'usage mémoire. Il a longtemps été reproché à Microsoft d'être trop gourmand en RAM.... donc les fonctions, faut les utiliser ! 1
MAM78 Posté(e) le 6 janvier 2018 Signaler Posté(e) le 6 janvier 2018 Merci pour ce petit rappel des basiques de la programmation
Lazer Posté(e) le 6 janvier 2018 Signaler Posté(e) le 6 janvier 2018 Cela dit, je parle là d'optimisation "atomiques", il existe ainsi plein de petits trucs et astuces à savoir.... MAIS on gagne là des pouillèmes de micro-secondes, souvent le temps perdu par un code l'est par une mauvaise logique, et il est parfois plus efficace de restructurer son code différemment que de se prendre la tête sur les optimisations atomiques.
Sakkhho Posté(e) le 6 janvier 2018 Signaler Posté(e) le 6 janvier 2018 Et une lampe wifi à côté de la tête toute là nuit ? Je suis pas trop parano mais j ai pris l habitude de mettre l iPhone en avion la nuit.
OJC Posté(e) le 6 janvier 2018 Auteur Signaler Posté(e) le 6 janvier 2018 Un p'tit WallPlug fait l'affaire
OJC Posté(e) le 14 janvier 2018 Auteur Signaler Posté(e) le 14 janvier 2018 Je publie une nouvelle version, m'étant rendu compte que dans certains cas, le VD détectait bien la remise sous tension de la lampe mais ne relançait pas la connexion pour en recevoir les informations de mise à jour. J'en ai profité pour nettoyer un peu le code et virer la fonction _y:help() qui au final prenait surtout beaucoup de place pour pas grand chose. Et le VD s'occupe aussi de nettoyer tout seul les variables globales lorsqu'un VD est supprimé ou en cas de changement d'ID. J'ai à cette occasion découvert que si on peut faire un api.delete dans une scène, ce n'est pas prévu pour les VD où il faut passer directement par Net.FHttp() pour pouvoir supprimer une variable globale...
Steven Posté(e) le 23 février 2018 Signaler Posté(e) le 23 février 2018 Je tombe sur ce post tardivement, voyant qu'on parle d'optimisation : getBtn=function(i,n) local c,r = 0, api.get("/devices/"..tostring(i))["properties"]["rows"] local nbr = #r for a=1, nbr do local nbra = #r[a].elements for b=1, nbra do c=c+1 if n == r[a].elements[b].name then return c end end end return nil end A quoi sert "c" ... pourquoi ne pas retourner directement "b" Ok,
Lazer Posté(e) le 23 février 2018 Signaler Posté(e) le 23 février 2018 LOL Magnifique exemple de ce que je disais précédemment : " il est parfois plus efficace de restructurer son code différemment " J'avoue que je n'ai même pas cherché à comprendre ce que faisait le code.... 1
OJC Posté(e) le 24 février 2018 Auteur Signaler Posté(e) le 24 février 2018 @Steven Parce que la valeur de a correspond au numéro de la ligne sur le VD, et celle de b correspond au numéro de l'élément au sein de cette ligne, d'où il suit qu'elle est réinitialisée à chaque itération de a et donc que retourner cette valeur de b comme résultat de la fonction ne correspond pas à ce qui est attendu. Sinon, pas trop froid, dehors ?
Steven Posté(e) le 26 février 2018 Signaler Posté(e) le 26 février 2018 C'est pas faux Oui, je me les cailles comme on dit vulgairement... température réelle -7, ressentie -13. Je suis en train du brûler du "pellet" comme jamais. C'est dans des moments comme celui-ci qu'on regrette de ne pas avoir un autre mode de chauffage. Après en ce qui concerne l'optimisation, perso, ma devise est : "préféré un code qui fait son travail à un code parfait qui ne fait rien".
pepite Posté(e) le 26 février 2018 Signaler Posté(e) le 26 février 2018 il y a 24 minutes, Steven a dit : préféré un code qui fait son travail à un code parfait qui ne fait rien" Et moi je fais des codes qui ne font rien et non parfait, la galère non ?
Steven Posté(e) le 26 février 2018 Signaler Posté(e) le 26 février 2018 Même pas vrai ... patate. 1 1
Eliah Posté(e) le 5 juillet 2018 Signaler Posté(e) le 5 juillet 2018 (modifié) bonjour, je test ce VD avec un Yeelight Smart LED Bulb et un Yeelight Lightstrip qui semblent ok avec l'open API mais le VD ne marche pas. J'ai mis l'ip et le port dans le VD mais il me met "état : hors ligne" alors que dans l'app Yeelight j'ai bien le contrôle des deux... une idée ? Quelqu'un a t-il essayé avec ce genre de produit et cela est-il fonctionnel chez quelqu'un ? Sur quel serveur les avez-vous enregistrer ? (singapour, chine, allemagne ?) Modifié le 5 juillet 2018 par Eliah
OJC Posté(e) le 5 juillet 2018 Auteur Signaler Posté(e) le 5 juillet 2018 Bonjour @Eliah Qu'est-ce que tu as dans le debug du main loop ?
OJC Posté(e) le 7 juillet 2018 Auteur Signaler Posté(e) le 7 juillet 2018 @Eliah Tu as activé le contrôle local dans l'application Yeelight (pour chaque lampe) ? C'est indispensable pour pouvoir commander les lampes depuis le réseau local.
Messages recommandés