Stocker des mesures en temps réel dans un fichier CSV

Stocker des mesures en temps réel dans un fichier CSV

Bien que les capteurs Yoctopuce disposent d'un enregistreur de données intégré, il est souvent utile de mémoriser les données dans une base de donnée ou un fichier CSV pour les traiter localement. Dans l'idéal, on aimerait récupérer les données en temps réel autant que possible, mais en se réservant la possibilité de récupérer les données enregistrées par le module en cas de perte de connexion temporaire. Et ça n'est pas si difficile à faire...

Un exemple d'application qui utilise cette technique combinant l'acquisition en temps réel et la récupération des données offline depuis l'enregistreur de données du module est l'outil Yocto-Visualization. Si vous activez l'utilisation du datalogger dans la configuration du graphique, vous constaterez qu'il est capable de récupérer tous les points de mesure intermédiaires en cas de coupure de réseau.

Nous vous proposons donc aujourd'hui un petit programme en Python qui illustre les techniques de programmation utilisées dans Yocto-Visualization pour obtenir ce résultat. En quelques dizaines de lignes, ce programme peut surveiller un nombre arbitraire de capteurs et en consigner les mesures dans l'ordre chronologique dans un fichier au format CSV - un fichier par capteur -, en récupérant automatiquement les données manquantes en cas de perte de connexion ou d'arrêt temporaire du programme.

Pour les gens pressés

Vous trouverez ce programme dans notre librairie de programmation pour Python, dans le répertoire Examples/Prog-SensorManager. Si vous voulez surveiller des capteurs en temps réel par des fichiers CSV et que vous être vraiment trop pressés pour faire votre propre adaptation de ce code, cela peut même être une solution prête à l'emploi. Il vous suffit de la lancer dans un shell Unix ou une fenêtre de commande Windows:

pi@raspberrypi ~ $ python3 SensorMonitor.py
Hit Ctrl-C to Stop
Device online : LIGHTMK1-956BE
Load missing data from LIGHTMK1-956BE.lightSensor : 100% done
18:36:23 Device offline : LIGHTMK1-956BE
18:36:28 Device online : LIGHTMK1-956BE
Load missing data from LIGHTMK1-956BE.lightSensor : 100% done
18:37:08 (clock ticking...)


Tant que le programme tourne, vous trouverez les données en temps réel dans le fichier LIGHTMK1-956BE.lightSensor.csv, comme vous pourrez le vérifier avec tail -f par exemple.

N'oubliez pas que pour que ce programme fonctionne, vous devrez préalablement:

  • Activer les timed reports (mesures à intervalle régulier) à la fréquence de votre choix sur chaque fonction de mesure qui vous intéresse
  • Activer l'enregistrement des mesures sur ces mêmes fonctions
  • Activer l'enregistreur de données sur le module

Ces opérations peuvent être faites avec le VirtualHub ou avec Yocto-Visualization.

Comment ça marche?

Le programme est structuré en trois parties:

  1. l'initialisation de la librairie Yoctopuce, qui se connecte à USB et/ou aux YoctoHubs
  2. les fonctions qui gèrent la connexion/déconnexion des modules Yoctopuce
  3. la classe SensorManager, dont on créera une instance par capteur à surveiller

L'initialisation


Pour initialiser la librairie Yoctopuce dans un mode tolérant aux déconnexions temporaires, on utilise les lignes suivantes:

if YAPI.PreregisterHub("usb", errmsg) != YAPI.SUCCESS:
    sys.exit("Init error: " + errmsg.value)

# On utilise les callbacks de (dé)connexion
YAPI.RegisterDeviceArrivalCallback(deviceArrival)
YAPI.RegisterDeviceRemovalCallback(deviceRemoval)


Pour contacter un ou des YoctoHubs, il suffit de rajouter d'autres appels à PreregisterHub avec l'adresse IP du ou des YoctoHubs. Tous les YoctoHubs seront gérés en parallèle de manière transparente.

Le corps du programme est très simple: il ne fait que donner la main à la librairie pour gérer les événements, et afficher le temps qui passe pour savoir qu'il fonctionne toujours:

while True:
    YAPI.UpdateDeviceList(errmsg)  # propage les (dé)connexions
    YAPI.Sleep(500, errmsg)        # propage les mesures
    # affiche continuellement l'heure courante
    now = datetime.datetime.now().strftime("%H:%M:%S")
    print("\b\b\b\b\b\b\b\b\b"+now, end=' ')


Gestion des connexions/déconnexions


Lorsqu'un module s'annonce pour la première fois au programme, le callback d'arrivée est automatiquement appelé, avec l'objet YModule correspondant en paramètre.

def deviceArrival(module):
    serial = module.get_serialNumber()
    print('Device online : ' + serial)


On aimerait énumérer les fonctions de type capteur qui s'y trouvent, pour associer à chacune une instance de SensorManager responsable de stocker ses mesures. Mais si le module a déjà été vu une fois par le passé, il faudrait ne le faire que la première fois. Nous allons donc utiliser une méthode très pratique de la librairie Yoctopuce: get_userData(). Cette méthode vous permet d'associer un objet de votre choix à chaque module Yoctopuce. Nous l'utiliserons pour stocker la liste des instances de SensorManager de chaque module. Voici donc la suite de la fonction deviceArrival:

    # Si le module est inconnu, énumère les capteurs qu'on y trouve
    sensorManagers = module.get_userData()
    if sensorManagers is None:
        sensorManagers = []
        sensor = YSensor.FirstSensor()
        while sensor:
            if sensor.get_module().get_serialNumber() == serial:
                # Pour chaque capteur, on instancie un SensorManager
                # et on l'ajoute à notre liste
                handler = SensorManager(sensor)
                sensorManagers.append(handler)
            sensor = sensor.nextSensor()
        module.set_userData(sensorManagers)


Il ne reste plus qu'à appeler dans tous les cas la méthode du SensorManager qui gère les nouvelles connexion de modules:

    # Notify the SensorManager about the arrival
    for handler in sensorManagers:
        handler.handleArrival()


Le méthode qui gère les déconnexion de modules n'a qu'à transmettre l'appel aux instances de SensorManager:

def deviceRemoval(module):
    print('Device offline : ' + module.get_serialNumber())
    sensorManagers = module.get_userData()
    for handler in sensorManagers:
        handler.handleRemoval()


La classe SensorManager


Les deux choses importantes que doit faire cette classe sont:

  • Stocker les données en temps réel;
  • Récupérer les données manquantes en cas de déconnexion.

Tout part de la méthode handleArrival, que nous avons appelée ci-dessus.

def handleArrival(self):
    # enregistre un callback pour recevoir les nouvelles mesures en temps réel
    self.sensor.registerTimedReportCallback(self.sensorTimedReportCallback)
    # récupère l'heure de la dernière mesure connue
    self.lastStamp = self.getLastTimestamp()
    # charge les mesures manquantes depuis le module
    print("Load missing data from %s :" % self.sensorName, end='     ')
    dataset = self.sensor.get_recordedData(self.lastStamp, 0)
    dataset.loadMore()
    progress = 0
    while progress < 100:
        progress = dataset.loadMore()
        print("\b\b\b\b%3d" % progress, end='%')
    details = dataset.get_measures()
    for measure in details:
        self.appendMeasureToFile(measure)
    print(' done')


Ces quelques lignes suffisent à faire la récupération des mesures manquantes en cas de déconnexion. Pour les mesures en temps réel, c'est encore plus simple:

def sensorTimedReportCallback(self, sensor, measure):
    # on s'assure de ne pas stocker de mesure à double
    if measure.get_endTimeUTC() > self.lastStamp:
        self.appendMeasureToFile(measure)


La méthode de stockage des valeurs (appendMeasureToFile) dépend de l'application. Dans notre cas, nous avons fait un simple fichier CSV, mais vous préférerez peut-être utiliser une base de données:

def appendMeasureToFile(self, measure):
    self.lastStamp = measure.get_endTimeUTC()
    utc = measure.get_endTimeUTC_asDatetime()
    stamp = utc.strftime(SensorManager.DATEFORMAT)
    value = measure.get_averageValue()
    with open(self.dataFile, 'a') as file:
        file.write("%s;%.3f\n" % (stamp, value))


De même, la méthode getLastTimestamp doit retourner l'heure de la dernière mesure stockée, et devra être adaptée en cas d'utilisation d'une base de donnée avec une requête de type SELECT MAX(`timestamp`) FROM `table`. Dans notre cas, on se contente de relire le timestamp de la dernière ligne du fichier CSV.

Conclusion

Voilà qui démontre qu'il suffit donc de peu de lignes de code pour faire une application qui utilise de manière optimale les capteurs Yoctopuce.

Si vous préférez travailler dans un autre langage que Python, la traduction de ces quelques lignes ne devrait pas vous prendre bien longtemps: les principes démontrés sont applicables dans tous les langages supportés par notre librairie, puisque nous offrons exactement les mêmes fonctions pour tous les langages. En cas de doute, n'hésitez pas à contacter le support Yoctopuce.

Commenter aucun commentaire Retour au blog












Yoctopuce, get your stuff connected.