Un client désire utiliser nos capteurs avec un logiciel destiné à des capteurs sur TCP/IP au standard NMEA (National Marine Electronic Association). En gros, un capteur NMEA envoie spontanément ses mesures à intervalle régulier, sous forme d'une ligne de texte avec des valeurs séparées par des virgules. La solution la plus simple consiste donc à faire une petite passerelle TCP qui lit n'import lequel de nos capteurs USB et envoie périodiquement les mesures sur TCP à chaque client connecté. Comme c'est une approche qui peut être utile à d'autres, nous en avons fait un petit code d'exemple.
Énoncé du problème
On désire pouvoir connecter n'importe capteur Yoctopuce par USB, et recevoir ses mesures périodiquement sur une connexion TCP en mode texte:
Architecture d'un serveur TCP/IP NMEA pour capteurs Yoctopuce
Par exemple, pour un capteur PT100 servant à mesurer la température de l'eau, on désire obtenir sur la connexion TCP des messages du type "$xxMTW" (Mean Temperature of Water):
$YSMTW,25.404,'C*39 $YSMTW,25.396,'C*35 $YSMTW,25.394,'C*37
Le signe $ au début est obligatoire. Les deux premières lettres sont arbitraires et les trois lettres suivantes identifient le type de mesure. L'étoile suivi du chiffre à la fin du message correspond au checksum calculé selon le standard NMEA.
Pour permettre une grande souplesse d'utilisation, on désire spécifier le code NMEA à utiliser (dans notre cas "MTW") dans le nom logique du capteur. Les capteurs auxquels aucun nom logique n'a été attribué seront automatiquement ignorés.
Réalisation
Nous avons choisi de réaliser ce programme en C/C++, afin qu'il soit le plus simple possible à déployer: un petit exécutable, disponible sur n'importe quel OS (Windows, Linux, Mac OS X) et qui puisse tourner aussi bien sur la ligne de commande pour les tests qu'être installé en service.
Pour permettre le fonctionnement en service, il est important de gérer correctement les connexions et déconnexions USB. En effet, on peut s'attendre à ce que le capteur soit branché aussi bien avant qu'après le démarrage du service. Le code d'initialisation du service aura donc l'aspect suivant:
YAPI::RegisterDeviceRemovalCallback(deviceRemoval);
YAPI::DisableExceptions();
if (YAPI::RegisterHub("usb", errmsg) != YAPI::SUCCESS) {
cerr << "RegisterHub error : " << errmsg << endl;
return 1;
}
A la connexion d'un capteur Yoctopuce, on va chercher toutes les fonctions de type "Sensor" disponibles sur le module et, si elles portent un nom logique, les configurer pour envoyer spontanément des mesures périodiques à la fréquence choisie. Les deux méthodes magiques dans ce code sont set_reportFrequency et registerTimedReportCallback: grâce à elles, le capteur Yoctopuce va prendre en charge tout seul la responsabilité de calculer la valeur moyenne sur un intervalle périodique donné, et d'appeler notre callback pour chaque nouvelle mesure.
{
string serial = m->get_serialNumber();
YSensor *sensor = YSensor::FirstSensor();
while(sensor) {
string name = sensor->get_logicalName();
if(name.length() > 0 &&
sensor->get_module()->get_serialNumber() == serial)
{
sensor->set_reportFrequency(Globalp.freq);
sensor->registerTimedReportCallback(sensorTimedReportCallBack);
}
sensor = sensor->nextSensor();
}
}
Le callback appelé à chaque mesure périodique se contentera d'envoyer la mesure au format choisi à chaque client TCP connecté au service. Nous avons mis un paramètre de configuration global permettant de choisir entre le véritable format NMEA ou un format simple séparé par des virgules (CSV), sans checksum et sans tralala.
{
const char *header = (Globalp.encodeNMEA ? "$YS" : "");
char buf[512];
snprintf(buf, sizeof(buf), "%s%s,%.3f,%s", header,
fct->get_logicalName().c_str(),
measure.get_averageValue(),
fct->get_unit().c_str());
if(Globalp.encodeNMEA) {
// NMEA format: compute checksum and append it to the message
int i, len = (int)strlen(buf);
unsigned chksum = 0;
for(i = 1; i < len; i++) {
chksum ^= (unsigned char)buf[i];
}
snprintf(buf+len, sizeof(buf)-len, "*%02X\r\n", chksum);
} else {
// raw format, just append CR-LF
strcat(buf, "\r\n");
}
TCPBroadcast(buf);
}
Dès lors, tout est géré par callback, aussi bien le hot-plug de modules que la gestion des mesures. La boucle principale du programme n'a donc qu'à accepter les connections, et appeler la fonction qui gère les callbacks lorsque c'est nécessaire:
TCPAccept(srvsock);
YAPI::UpdateDeviceList(errmsg);
YAPI::Sleep(300, errmsg);
}
La fonction TCPAccept se contente d'accepter les connections entrantes sur un nouveau socket, et de l'ajouter à une liste chaînée.
La fonction TCPBroadcast utilisée dans le callback n'a donc qu'à parcourir cette chaîne pour envoyer le message sur chaque socket. N'ayant rien de spécifique aux modules Yoctopuce, ce code ne sera pas détaillé ici, de même que toute la mécanique Windows pour la gestion du fonctionnement en mode service.
Informations pratiques
Options supportées sur la ligne de commande:
demo.exe [-n] [-p <port>] [-f <freq>] [-i|-u|-d] -n : ajoute le checksum NMEA à chaque ligne -p <port> : choix du port TCP/IP utilisé par le serveur -f <freq> : choix de la fréquence des mesures (par ex. 1/s, 20/m, 12/m, 30/h)
Options spécifiques à Windows:
-i : installe le programme comme service, avec les options spécifiées -u : désinstalle le programme de la liste des services
Option spécifique à Unix:
-d : continue l'exécution en tâche de fond (daemon)
Si ce programme vous intéresse, en l'état ou pour l'adapter pour vos besoins, vous trouverez le code source complet, les makefiles, les projets et même des binaires compilés parmi les exemples de notre dernière librairie C++, dans le répertoire Prog-NMEA.