Comment lire efficacement un capteur

Comment lire efficacement un capteur

L'API Yoctopuce offre plusieurs manières de communiquer avec les modules Yoctopuce: le polling, les callbacks de changement de valeur et les callbacks périodiques. On a déjà eu l'occasion de le mentionner de temps à autre dans ce blog, mais on n'a jamais pris le temps de présenter ces différentes techniques en détail. C'est aujourd'hui chose faite :-)



Le polling

Le polling est la manière la plus simple et la plus intuitive. Elle consiste à interroger explicitement le module et à attendre la réponse. Voici un exemple de base écrit en Python qui utilise la classe YSensor pour interroger un Yocto-Temperature dont le numéro de série est "TMPSENS1-00BDA".


import sys
from yocto_api import *

# Sets up the API to use local USB devices
errmsg=YRefParam()
if YAPI.RegisterHub("usb", errmsg)!= YAPI.SUCCESS: sys.exit(errmsg.value)

# Finds the temperature sensor named TMPSENS1-00BDA.temperature
sensor=YSensor.FindSensor("TMPSENS1-00BDA.temperature")
if not sensor.isOnline() : sys.exit("device not found")

print(sensor.get_currentValue()+" "+sensor.get_unit())
 



Il suffit donc en tout et pour tout de 4 simples appels de fonction pour lire un capteur:

  1. Un appel pour initialiser l'API et spécifier si l'on va travailler avec des capteurs USB ou en réseau
  2. Un appel pour trouver le capteur de température nommé TMPSENS1-00BDA.temperature
  3. Un appel, optionnel bien que recommandé, pour vérifier que le capteur est connecté
  4. Un appel pour récupérer la valeur du capteur


Cependant, cette simplicité cache un problème d'efficacité: Imaginez que vous ayez un travail à faire et que votre manager vous appelle régulièrement au téléphone pour savoir où vous en êtes. S'il se contente de vous appeler une fois par jour, ça risque d'être un peu pénible mais relativement gérable. En revanche s'il lui prend l'envie de vous appeler toutes les trois minutes (j'exagère à peine), vous allez passer plus de temps au téléphone qu'à faire avancer votre travail. Sans compter que ça ne va pas trop améliorer la productivité du manager en question. Bref, il est certainement plus censé que ce soit vous preniez l'initiative d'appeler votre manager de temps en temps pour rendre compte des avancées significatives de votre travail. Le même raisonnement s'applique aux modules Yoctopuce.

Les callbacks de changement de valeur

Il est possible de demander à l'API Yoctopuce d'appeler une de vos fonctions à chaque fois que la valeur d'un capteur change de manière significative. Le code est légèrement plus long, mais nettement plus efficace:


import sys
from yocto_api import *

def MyValueCallback(sensor,value):
    print(value+" "+sensor.get_unit())

# Sets up the API to use local USB devices
errmsg=YRefParam()
if YAPI.RegisterHub("usb", errmsg)!= YAPI.SUCCESS: sys.exit(errmsg.value)

# Finds the temperature sensor named TMPSENS1-00BDA.temperature
sensor=YSensor.FindSensor("TMPSENS1-00BDA.temperature")

# Registers a callback which  is called each time the sensor value changes
sensor.registerValueCallback(MyValueCallback)

while True:
    YAPI.Sleep(1000)
 



On a défini une fonction MyValueCallback et on a demandé à l'API de l'appeler automatiquement à chaque fois que la valeur du capteur change de manière significative. Afin d'éviter les surprises désagréables, les fonctions de callback ne ne sont pas appelées n'importe quand, mais seulement pendant l'exécution des fonctions YAPI.Sleep() ou YAPI.HandleEvents(). Vous devrez donc appeler l'une ou l'autre de ces fonctions de manière régulière.

Si vous ne souhaitez pas truffer votre code d'appels à YAPI.Sleep() ou YAPI.HandleEvents(), pour pouvez parfaitement exécuter cet appel depuis un thread parallèle. Mais prenez garde aux situations de compétition ou autres étreintes fatales.


import sys
from threading import *
from yocto_api import *

def YoctoMonitor():
  while True:
      YAPI.Sleep(1000)

def MyValueCallback(fct,value):
    print(value+" "+fct.get_unit())

# Sets up the API to use local USB devices
errmsg=YRefParam()
if YAPI.RegisterHub("usb", errmsg)!= YAPI.SUCCESS: sys.exit(errmsg.value)

# Finds the temperature sensor named TMPSENS1-00BDA.temperature
sensor=YSensor.FindSensor("TMPSENS1-00BDA.temperature")

# Registers a callback which  be called each the sensor value changes
sensor.registerValueCallback(MyValueCallback)

# Starts a thread to automatically handle Yoctopuce devices
t = Thread(target=YoctoMonitor)
t.start()

# Does nothing at all
while True: pass
 



Notez que les callbacks ne sont pas réservés qu'aux capteurs, il est aussi possible de mettre en place un callback sur un actuateur. Par exemple, il est parfaitement possible d'être prévenu à chaque fois qu'un relais change d'état grâce à un callback similaire.

Gestion du Plug and play

La programmation par callback est aussi très intéressante pour gérer la liste des modules connectés. Les modules Yoctopuce étant des modules USB, il y a de grandes chances pour qu'ils soient connectés et déconnectés plusieurs fois durant l'exécution votre l'application. Vous pourriez bien sûr procéder à une énumération exhaustive à intervalle régulier. Mais il se trouve que les callbacks permettent de tenir à jour une liste de modules bien plus facilement.


import sys
from yocto_api import *

devicelist = []

def deviceArrival(m):
  devicelist.append(m)
  print("new device: "+m.get_serialNumber()+" / "+str(len(devicelist))+' device(s) connected')

def deviceRemoval(m):
  devicelist.remove(m)
  print("device left: "+m.get_serialNumber()+" / "+str(len(devicelist))+' device(s) connected')

# Sets up the API to use local USB devices
errmsg=YRefParam()
if YAPI.RegisterHub("usb", errmsg)!= YAPI.SUCCESS: sys.exit(errmsg.value)

# deviceArrival and deviceRemoval are called each time a device
# is plugged in or removed.
YAPI.RegisterDeviceArrivalCallback(deviceArrival)
YAPI.RegisterDeviceRemovalCallback(deviceRemoval)

while True:
    YAPI.UpdateDeviceList()
    YAPI.Sleep(500)
 



Les callbacks arrival et removal ne sont appelés que pendant l'appel à UpdateDeviceList. Si cette fonction est simple à appeler, elle déclenche en interne un processus d'énumération assez lourd. Il n'est donc pas recommandé de l'appeler en boucle des centaines de fois par seconde.

Les callbacks périodiques

Les callbacks périodiques sont une troisième technique proposé par l'API Yoctopuce. En termes fonctionnels, elle est à mi-chemin entre le polling et les callbacks normaux, puisqu'elle permet d'appeler un callback à intervalle régulier même lorsque la valeur d'un capteur n'a pas changé. La mise en place d'un callback périodique se fait de la même manière qu'avec un callback de changement de valeur, à deux différence près.

  • Il ne faut pas pas oublier de préciser à quel rythme le callback doit être appelé à l'aide de la fonction set_reportFrequency. Le paramètre est une chaîne de caractères au format nombre d'appel / unité de temps, où l'unité de temps peut être soit la seconde (s), la minutes (m) ou l'heures (h).
  • Au lieu de de recevoir directement une valeur, le callback reçoit un objet YMeasure qui permet de connaître non seulement la valeur moyenne, mais aussi les maxima, minima, ainsi que l'heure de début et de fin de l'intervalle.



import sys,datetime
from yocto_api import *

def MyTimedCallback(fct,measure):
    start = measure.get_startTimeUTC()
    now = datetime.datetime.fromtimestamp(start).strftime('%Y-%m-%d %H:%M:%S')
    print( now+' : ' + str(measure.get_averageValue())+" "+fct.get_unit())

# Sets up the API to use local USB devices
errmsg=YRefParam()
if YAPI.RegisterHub("usb", errmsg)!= YAPI.SUCCESS: sys.exit(errmsg.value)

# Finds the temperature sensor named TMPSENS1-00BDA.temperature
sensor=YSensor.FindSensor("TMPSENS1-00BDA.temperature")

# Registers a callback which  is called once per second
sensor.set_reportFrequency("1/s")
sensor.registerTimedReportCallback(MyTimedCallback)

while True:
    YAPI.Sleep(1000)
 



Si l'unité de temps précisé lors de l'appel à set_reportFrequency est la seconde (de une à cent mesures par seconde), la valeur retournée n'est pas une valeur moyennée mais une valeur instantanée. Les mesures sont moyennées lorsque la fréquence est spécifiée en mesures par minute ou par heure. Il est possible d'obtenir jusqu'à 60 mesures moyennées par minute, en définissant la fréquence de callback à "60/m" au lieu de "1/s".

Notez qu'il est inutile de mettre en place des callbacks périodiques avec une fréquence plus élevée que la fréquence de rafraîchissement du capteur correspondant. Par exemple, dans le cas de notre Yocto-Temperature, cette fréquence est de 10Hz. Mettre en place un callback périodique qui tournera plus vite que 10/s ne servira à rien à part gaspiller de la bande passante.

Pour finir

Un des effets de bord intéressants de la programmation par callback est que vous n'avez pas forcement besoin de gérer explicitement la présence des modules. Si vous mettez en place un callback mais que le module correspondant n'est pas/plus là, votre callback ne sera tout simplement pas appelé. Si le module revient, le callback reprendra.. simple comme bonjour.

Un deuxième effet de bord est une plus grande robustesse aux pertes de paquets USB qui se produisent parfois sur un bus USB trop chargé, ou avec les mini-PC dont le support USB est un peu borderline. En effet, dans une communication par polling (question/réponse), la perte d'un paquet non prévisible entraîne forcément un timeout. Au contraire, dans le cas d'une transmission de valeur par callback (notification asynchrone), la perte d'un paquet est limitée à la perte d'une mesure mais ne cause pas de timeout significatif.

Voilà, on a fait le tour de la question, vous savez maintenant tout ce qu'il y à savoir sur les diverses manières d'interroger un module Yoctopuce. Tous les exemples de cet article sont en Python, mais naturellement, ces techniques sont disponibles dans tous les langages de programmation. Amusez-vous bien.

Commenter aucun commentaire
Retour au blog












Yoctopuce, get your stuff connected.