Vous vous rappelez les boutons Flics dont on avait parlé il y a quelques années ? Figurez-vous qu'on continue à les utiliser en interne. Entre-temps, Shortcut Labs a sorti une version 2 de ces boutons et de leur Hub. Il se trouve que le nouveau modèle, appelé Flic Hub LR, peut faire tourner du code JavaScript écrit par l'utilisateur et permet ainsi d’implémenter des comportements un peu plus complexes que Allumé/Eteint. Évidement, on s'est demandé si on pouvait faire tourner la version JavaScript de l'API Yoctopuce sur ces nouveaux Hubs.
Flic Hub LR et bouton Flic 2
Autant tuer le suspense tout de suite, l'API Yoctopuce ne marche pas sur le Flic Hub LR en raison des nombreuses limitations qu'imposent ces hubs.
- Tout le code doit tenir dans un seul fichier.
- Les API réseau standards ont été remplacées par des appels spécifiques.
- Le Hub utilise une version pré-2015 de JavaScript: pas de primitives let, class, async, await...
Bref, essayer d'utiliser l'API Yoctopuce standard ressemble fortement à une impasse. En revanche, rien ne nous empêche d'utiliser l'interface REST des modules Yoctopuce pour écrire une petite librairie qui imitera le comportement de base des librairies officielles. C'est juste qu'il va falloir ressortir de leur carton et dépoussiérer les vieilles techniques de programmation JavaScript qu'on utilisait il y a 10 ans.
Fonctions générales
Notre librairie aura besoin de deux fonctions essentielles.
getFunctionURL
La première fonctionnalité à implémenter est une fonction qui soit capable de retrouver l'URL d'une fonction Yoctopuce en fonction de l'adresse IP du hub qui est censé l'héberger, de son type et de son nom matériel ou logique. Cette fonction utilise des fonctionnalités réseau, elle doit donc être asynchrone. Mais comme les primitives async, await ne sont pas disponibles, on s'est rabattu le vieux système de callback. Si l'appel s'est bien passé, le callback success est appelé avec l'URL en paramètre, en cas d'échec le callback failed est appelé avec le message d'erreur en paramètre.
{ ... }
On vous épargne les détails de l'implémentation, mais le principe de fonctionnement est le suivant:
Si on considère que l’adresse du hub donnée en paramètre est W.X.Y.Z:abcd, la fonction commence par essayer de récupérer la page http://W.X.Y.Z:abcd/api/services.json. Il s'agit d'une structure JSON qui décrit tous les modules et les fonctions disponibles sur un hub. Pour illustrer, voici un exemple simplifié d'un VirtualHub auquel sont connectés un Yocto-Light-V3 et un Yocto-Temperature.
[ {"serialNumber":"VIRTHUB0-3880db7f12","logicalName":"",...},
{"serialNumber":"LIGHTMK3-1222A6","logicalName":"",...},
{"serialNumber":"TMPSENS1-1C0FA2","logicalName":"",...}
],
"yellowPages":
{ "Network":
[{...,"hardwareId":"VIRTHUB0-3880db7f12.network",
"logicalName":"VIRTHUB0-1c75d4",...}],
"DataLogger":
[{...,"hardwareId":"LIGHTMK3-1222A6.dataLogger","logicalName":"",...},
{...,"hardwareId":"TMPSENS1-1C0FA2.dataLogger","logicalName":"",...}
],
"LightSensor":
[{...,"hardwareId":"LIGHTMK3-1222A6.lightSensor","logicalName":"",...}],
"Temperature":
[{...,"hardwareId":"TMPSENS1-1C0FA2.temperature",
"logicalName":"myTemp",...}]
}
}
Cette structure de données est divisée en deux listes:
- whitePages: la liste des modules connectés avec, entre autres, leur numéro de série et leur nom logique
- yellowPages: la liste de toutes les fonctions connectées regroupées par type. Chaque entrée est composée d'un ensemble de propriétés comprenant le nom matériel et le nom logique.
Par conséquent, si le numéro de série ou le nom logique du module est spécifié dans functionName, getFunctionURL va parcourir en premier les whitePages pour retrouver le module qui correspond.
Elle va ensuite parcourir les yellowPages pour trouver l'entrée dont
- Le type correspond au paramètre functionType
- Le hardware id ou le logical name correspond au nom de fonction spécifié du functionName
- Le cas échéant, le numéro de série du module correspond au paramètre functionName
Si le paramètre functionName est vide, la getFunctionURL va renvoyer la première fonction du bon type trouvé dans les yellowPage.
Le résultat est une URL qui aura la forme suivante:
http://adresse/bySerial/numero de série/api/Nom matériel de fonction
Par exemple, dans le cas d'une fonction Temperature.
http://192.168.0.32:4444/bySerial/METEOMK2-20783D/api/temperature
getData
C'est la deuxième fonction générique indispensable qui sert à récupérer l'état complet d'une fonction Yoctopuce. Son prototype est:
function getData(success, failed) {...}
Cette fonction getData effectue une requête HTTP avec l'URL calculée par getFunctionURL ce qui lui permet d'obtenir une structure JSON qui décrit l'état de la fonction correspondante à l'URL, par exemple dans le cas d'une fonction "Temperature":
"advertisedValue":"25.1",
"unit":"'C",
"currentValue":1644953,
"lowestValue":1618739,
"highestValue":1656225,
...
}
Si tout se passe bien getData va essayer de parser cette structure et de passer le résultat à la fonction success. Si un problème apparaît alors le message d'erreur correspondant est passé à failed.
A ce stade, le plus dur est fait, il ne reste plus qu'à coder les classes qui gèrent le comportement des fonctions Yoctopuce.
Lire un capteur: YTemperature
Pour implémenter le contrôle de la fonction YTemperature, on crée une fonction JavaScript qui mime le comportement d'une classe. Rappelez-vous que la primitive "Class" n'est pas disponible sur le Flic Hub LR.
{ this.type = "Temperature"
this.get_data = generic_getData;
this.get_url = generic_getUrl
this.addr = addr;
this.name = name;
this.url = "";
this.get_currentValue = YTemperature_getCurrentValue;
this.get_unit = YTemperature_getUnit;
}
YTemperature.get_currentValue
Pour récupérer la valeur de notre capteur de température, il suffit de récupérer les données JSON qui correspondent à son URL. La valeur d'un capteur se trouve dans le champs entier currentValue. Pour obtenir sa valeur en virgule flottante, il faut diviser currentValue par 65536.0. C'est valable pour toutes les valeurs de tous les capteurs Yoctopuce. Si tout se passe bien, le résultat est passé au callback success, dans le cas contraire le message d'erreur décrivant le problème est passé au callback failed.
{ this.get_data(
function (data) { if (success) success(data.currentValue / 65536.0);},
function (err) { if (failed) failed(err) }
);
}
Contrôler un actuateur: YRelay
Les relais figurent parmi les actuateurs Yoctopuce les plus simples, leur contrôle est implémenté à l'aide d'une fonction/classe très similaire à celle de YTemperature
{ this.type = "Relay"
this.get_data = generic_getData;
this.get_url = generic_getUrl
this.addr = addr;
this.name = name;
this.url = "";
this.pulse = YRelay_pulse;
this.set_state = YRelay_set_state;
this.toggle = YRelay_toggle;
}
YRelay.set_state
Pour changer l'état d'un relais, il faut appeler son URL avec le paramètre "state=" zéro pour l'état A, un pour l'état B ou X pour inverser l'état. Pour ce faire, set_state commence par appeler sa méthode get_url qui lui permet de retrouver puis mémoriser son URL (this.url) puis effectue une requête sur cette URL avec le paramètre state à 0 ou 1.
{ var self = this;
if (self.url == "")
return self.get_url(
function () {self.set_state(state, success, failed)},
failed
);
var cmd = self.url + ".json?state=" + (state ? "1" : "0");
AsyncRequest(cmd, success, function (err) {if (failed) failed(self.errDecode(err));});
}
YRelay.toogle
La fonction toogle est encore plus simple à implémenter
{ var self = this;
if (self.url == "")
return self.get_url(
function (){self.toggle(success, failed) },
failed
);
var cmd = self.url + ".json?state=X";
AsyncRequest(cmd,
success,
function (err){ if (failed) failed(self.errDecode(err));}
);
}
Notez que le callback success() sera appelé avec la structure de données correspondant au relais, ce qui permet de savoir dans quel état est le relais après l'appel sans avoir à faire une seconde requête.
La liste des commandes disponibles pour l'URL de chaque fonction n'est pas officiellement publiée, mais vous pouvez assez facilement trouver toutes ces commandes en consultant le code source de n'importe laquelle des librairies Yoctopuce.
Utilisation
On se retrouve avec une librairie facile à utiliser et qui offre des possibilités assez similaires aux fonctions de base de l'API officielle. Pour référencer une fonction, on peut utiliser presque n'importe quelle combinaison de noms matériels et logiques:
// l'adresse 192.168.0.32:4444
var r = new YRelay("192.168.0.32:4444", "");
// référence le premier relais appelé "myRelay"
//disponible à l'adresse 192.168.0.32:4444
var r = new YRelay("192.168.0.32:4444", "myRelay");
// référence le relais numéro 1 disponible sur le
// Yocto-MaxiPowerRelay MXPWRRLY-12345 à l'adresse
// 192.168.0.32:4444
var r = new YRelay("192.168.0.32:4444", "MXPWRRLY-12345.relay1");
// référence le relais appelé "myRelay" disponible
// sur le module nommé "myModule" à l'adresse 192.168.0.32:4444
var r = new YRelay("192.168.0.32:4444", "myModule.myRelay");
Pour changer l'état d'un relais, il suffit alors d'un simple appel à sa méthode Set_state
Pour lire un capteur, le code est assez similaire, si ce n'est que le résultat n'est pas directement renvoyé par la méthode get_state mais passé en paramètre à un callback.
// disponible à l'adresse
var t = new YTemperature("192.168.0.32:4444", "");
// récupère la valeur du capteur
t.get_currentValue(
function(value) {console.log("value="+value);},
function(err) {console.log("Error: "+value);}
);
Limitations
Évidement, cette librairie simplifiée n'est pas 100% équivalente aux librairies Yoctopuce officielles dans la mesure où elle ne permet de faire que du polling. Mais c'est tout à fait suffisant pour les applications Yoctopuce qui se limitent à des appels Yoctopuce occasionnels. L'absence de module est traité gracieusement en appelant les callback Fail(), mais ce système n'a pas la puissance des callbacks arrival/removal de l'API originale.
De plus, cette librairie ne peut interfacer que des modules accessibles via un YoctoHub ou un VirtualHub , en d'autres termes elle n'émule que le mode réseau de l'API Yoctopuce classique. Le mode USB est clairement hors de portée.
Application
Pour tester notre librairie, on a construit un petit système qui affiche la température sur un panneau de LEDs et enclenche un ventilateur quand la température passe au-dessus d'un certain seuil, à moins que le ventilateur ne soit déclenché manuellement par un appui sur un bouton Flic (override).
var r = new YRelay("192.168.0.20", "");
var t = new YTemperature("192.168.0.21", "myTemp");
var c = new YColorLedCluster("192.168.0.22", "");
function run()
{var delta=0.25 // Schmitt trigger
if (todo == ACTION_DONOTHING)
t.get_currentValue(
function (res)
{ console.log("temp= "+res.toFixed(1))
if ((res >= ts + delta) && (lastTemp < ts + delta))
{override=false; todo = ACTION_ON;}
if ((res <= ts - delta) && (lastTemp > ts - delta))
{override=false; todo = ACTION_OFF;}
if (lastTemp!=res) refreshNeeded=true;
lastTemp = res;
},
function (err) { console.log(err); });
switch (todo)
{ case ACTION_ON:
console.log("ON");
r.set_state(1, null, logError)
currentState = ACTION_ON;
todo = ACTION_DONOTHING;
refreshNeeded =true;
break;
case ACTION_OFF:
console.log("OFF");
refreshNeeded =true;
r.set_state(0, null, logError)
currentState = ACTION_OFF;
todo = ACTION_DONOTHING;
break;
}
if (refreshNeeded)
{ var buffer = new Uint32Array(512);
var color = 0x101000;
if (lastTemp<ts-delta) color= 0x002000;
if (lastTemp>ts+delta) color= 0x200000;
DrawTextinBuffer(buffer, lastTemp.toFixed(1)+"°C" , 0, FontSmall, color)
var state = (currentState==ACTION_ON?"ON":"OFF")
DrawTextinBuffer(buffer, state , 30, FontSmall, 0x101010);
if (override) DrawTextinBuffer(buffer, "OVRD" , 45, FontSmall, 0x04040A)
c.rgbColorArray(0,buffer,null,logError );
}
}
// flic button control
buttonManager.on("buttonSingleOrDoubleClickOrHold",
function (obj)
{ var button = buttonManager.getButton(obj.bdaddr);
if (obj.isSingleClick)
{ console.log("Click on " + obj.bdaddr)
switch (currentState)
{ case ACTION_ON: todo = ACTION_OFF; override =true; run(); break;
case ACTION_OFF: todo = ACTION_ON; override =true; run(); break;
}
}
});
console.log("hello");
setInterval( run, 1000);
Et pour vous prouver que ça marche, on a fait une petite vidéo:
Et bien sûr, vous pouvez télécharger le code complet de cette application. Le code ne traite que les fonctions Temperature, Relay et ColorLedCluster, mais il serait assez facile de rajouter le support pour d'autres fonctions en se basant sur le code existant.
Ce qu'on pense du Flic Hub SDK
Honnêtement, même si on ne s'attendait pas à des miracles, on est globalement un peu déçu par le Flic Hub SDK:
Le fait que Flic Hub SDK soit basé sur une veille version de JavaScript est assez handicapant. Comme toutes les fonctions qui font des entrées/sorties doivent être asynchrones, cela oblige à adopter un style de programmation entièrement basé sur des callbacks, ce qui rend le code très vite lourd et illisible, sans compter que cela nécessite un bon niveau en programmation pour arriver à écrire du code qui marche comme on s'y attend.
L'IDE qui permet de coder pour le Flic Hub SDK est "cloud-based", ce qui pose plusieurs problèmes:
- L'éditeur proposé est assez minimaliste: il offre la colorisation syntaxique et... c'est tout. Pas de refactoring, pas de reformatage de code, même pas de search and replace. Si vous avez l'habitude de coder avec un éditeur moderne configuré en fonction de vos petites habitudes de programmation, l'éditeur du SDK va très vite vous agacer.
- Si Shortcut Labs venait à disparaître, la possibilité de coder pour le Flic Hub LR disparaîtra avec. Et ça, ça devrait être marqué en gros sur l'emballage.
On n'est pas sûr qu'il soit possible de faire tourner plusieurs scripts en parallèle sur le Flic Hub LR et apparemment il n'y a pas de debugger. De plus la version 3.0.12 du firmware qu'on avait à disposition a une vilaine tendance à crasher à cause de ce qui ressemble à un problème de stack interne. Ce bug sera, parait-il, corrigé dans une prochaine version.
Bref, si la possibilité de faire tourner son propre code sur un Flic Hub LR est en soi bienvenue, il ne faut pas en attendre trop et veiller à se limiter à des applications très simples, sous peine de s'exposer à de cruelles déconvenues.
Conclusion
Bien que cet article soit basé sur un matériel très spécifique, à savoir le Flic Hub LR , il peut servir d'inspiration pour écrire, si le besoin s'en faisait sentir, votre propre libraire Yoctopuce dans n'importe quel langage de programmation pour peu que ce langage soit capable de faire une requête HTTP.
Pendant qu'on y était, on a aussi mis à jour l'application Yocto-Flic pour qu'elle fonctionne avec la nouvelle génération de boutons Flic 2, les anciens modèles multicolores ayant l'air d'avoir disparu du paysage.