Récupérer des mesures enregistrées à travers Internet

Récupérer des mesures enregistrées à travers Internet

Presque tous nos capteurs disposent d'un data logger qui permet d'enregistrer automatiquement les valeurs mesurées sur le capteur lui-même. Un des intérêts du datalogger est qu'il continue à enregistrer même lorsque la connectivité est perdue ou que l'application s'arrête. Lorsque l'application reprend le contrôle du capteur, il est alors possible de retrouver les mesures passées. Mais jusqu'à présent, il était impossible de lire les données du datalogger à travers un callback HTTP. Grâce au nouveau mode callback WebSocket, c'est possible. Cette semaine, nous allons écrire une application Web Java qui illustre cette fonctionnalité.

Cette application web est une variante de l'application que nous avions réalisée avec notre station météo. Elle permet d'afficher le graphique des valeurs mesurées par un senseur sur un Yocto-MaxiDisplay. L'écran et le senseur sont branchés chacun sur un YoctoHub, qui se connecte à l'aide d'un callback WebSocket à l'application web qui sert de hub central. En hébergeant l'application web sur Internet, il est possible d'installer le senseur dans un bâtiment et l'écran dans un autre bâtiment à l'autre bout de la planète sans modifier la configuration des firewalls, car ce sont les YoctoHubs qui établissent la connexion avec l'application.

L'écran et le Yocto-Temperature peuvent être placés dans deux bâtiments
L'écran et le Yocto-Temperature peuvent être placés dans deux bâtiments


Pour notre application précédente basée sur le mécanisme de callback HTTP, qui ne permet ni de maintenir la connexion longtemps, ni d'utiliser le datalogger, nous avions configuré les Yoctohubs pour qu'ils se connectent au serveur toutes les 5 minutes, et les mesures étaient stockées dans une base de données. Avec les callbacks WebSocket, il est désormais possible d’établir une connexion persistante et bidirectionnelle avec l'application. Cela veut dire qu'il est possible d'afficher la valeur d'un senseur en direct, mais surtout qu'il est possible de se passer de la base de données en recherchant les mesures directement dans le datalogger du capteur.

Contrairement aux callbacks HTTP, les callbacks WebSocket permettent d'établir une connexion durable et réellement bidirectionnelle avec le serveur
Contrairement aux callbacks HTTP, les callbacks WebSocket permettent d'établir une connexion durable et réellement bidirectionnelle avec le serveur


Utiliser la librairie Java avec support WebSocket


Pour réaliser cette application, nous allons utiliser la librairie Java avec le support des callbacks WebSocket. Malheureusement, le support des WebSocket est requiert JavaEE (Java Enterprise Edition). Pour ne pas casser la compatibilité ascendante de notre librairie, nous avons décidé de dupliquer la librairie Java et d'en proposer deux versions. La version traditionnelle, qui se trouve dans le répertoire yoctolib, n'inclut pas le support WebSocket mais peut être compilé avec n'importe quel JDK 1.7 ou 1.8. La nouvelle version, qui inclut le support WebSocket, nécessite JavaEE. Cette nouvelle version est disponible dans le répertoire yoctolib-jEE du fichier zip.

Hormis le support WebSocket, ces deux versions sont identiques et vont être maintenues de la même manière et au même rythme. Elles se trouvent du reste dans la même archive. Si vous ne comptez pas utiliser les WebSocket vous pouvez continuer à utiliser les mêmes fichiers sources ou fichiers JAR. Si, au contraire, vous avez besoin d'utiliser les WebSocket, il faut utiliser les fichiers sources qui sont dans le répertoire yoctolib-jEE ou le fichier jar yoctolib-jEE.jar. Nous avons aussi publié cette librairie sur Maven Central, et il est possible de l'utiliser dans vos projets Maven en ajoutant dans votre pom file la dépendance suivante:

<dependency>
    <groupId>com.yoctopuce.java</groupId>
    <artifactId>yoctolib-jee</artifactId>
    <version>1.10.23400</version>
</dependency>


L'application


Notre application est composée de trois classes : WebSocketEndpoint, WorkerThread et SensorCache. La classe principale est WorkerThread. Comme son nom le laisse penser, il s’agit d'un thread unique qui est démarré lors de la première connexion. La méthode run() contient toute la logique de notre application.

La subtilité de cette classe est la manière de détecter la liste des modules Yoctopuce utilisables. Au lieu de faire une énumération explicite, nous avons deux méthodes de callback qui sont appelées dès qu'un nouveau module est détecté par la librairie Yoctopuce et dès qu'un module ne l'est plus. Ces méthodes de callback n'ont rien de nouveau.

En général, on enregistre une seule fois un YoctoHub, et on utilise UpdateDeviceList() pour détecter quand un module est physiquement branché ou débranché sur un PC ou un YoctoHub. Dans notre cas, c'est un peu différent: on cherche à détecter quand un hub se connecte à notre application. La boucle principale exécute UpdateDeviceList() même si aucun YoctoHub n'est connecté. Lorsqu'une connexion entrante est détectée, on va enregistrer le YoctoHub qui s'est connecté, et lors de la prochaine exécution tous les modules de ce YoctoHub vont être détectés. De la même manière, lorsque la connexion est terminée, on va dé-enregistrer le YoctoHub pour que les modules ne soient plus listés.

public class WorkerThread implements Runnable, YAPI.DeviceArrivalCallback, YAPI.DeviceRemovalCallback, YSensor.UpdateCallback
{
    private HashMap<String, SensorCache> _sensors_hwid = new HashMap<>();
    private ArrayList<String> _displays_hwid = new ArrayList<>();

    @Override
    public void run()
    {
        YAPI.RegisterDeviceArrivalCallback(this);
        YAPI.RegisterDeviceRemovalCallback(this);
        System.out.printf("worker thread started\n");
        while (_mustRun) {
            try {
                YAPI.UpdateDeviceList();
                for (String disp_hwid : _displays_hwid) {
                    YDisplay ydisplay = YDisplay.FindDisplay(disp_hwid);
                    ...
                    sensorCache.refresh();
                    repaintDisplay(sensorCache, ydisplay);
                }
                YAPI.Sleep(1000);
            } catch (YAPI_Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void addSession(Session session)
    {
        try {
            YAPI.PreregisterHubWebSocketCallback(session);
        } catch (YAPI_Exception e) {
            e.printStackTrace();
        }
    }

    public void removeSession(Session session)
    {
        YAPI.UnregisterHubWebSocketCallback(session);
    }

    @Override
    public void yDeviceArrival(YModule module)
    {
        try {
            ArrayList<String> functionIds = module.get_functionIds("Sensor");
            String serialNumber = module.get_serialNumber();
            for (String funid : functionIds) {
                String hwid = serialNumber + "." + funid;
                if (!_sensors_hwid.keySet().contains(hwid)) {
                    YSensor sensor = YSensor.FindSensor(hwid);
                    sensor.set_logFrequency("12/h");
                    SensorCache sensorCache = new SensorCache(sensor);
                    _sensors_hwid.put(hwid, sensorCache);
                }
            }
            if (functionIds.size() > 0) {
                String hwid = serialNumber + ".dataLogger";
                YDataLogger dataLogger = YDataLogger.FindDataLogger(hwid);
                dataLogger.set_autoStart(YDataLogger.AUTOSTART_ON);
                dataLogger.set_recording(YDataLogger.RECORDING_ON);
                module.saveToFlash();
            }
            functionIds = module.get_functionIds("Display");
            for (String funid : functionIds) {
                String hwid = serialNumber + "." + funid;
                if (!_displays_hwid.contains(hwid)) {
                    YDisplay display = YDisplay.FindDisplay(hwid);
                    display.resetAll();
                    _displays_hwid.add(hwid);
                }
            }
        } catch (YAPI_Exception e) {
            e.printStackTrace();
        }
    }

    ...
}


Le reste de la méthode est une simple boucle qui rafraîchit chaque seconde les Yocto-MaxiDisplay qui sont connectés. Les données à afficher sont prises directement du datalogger à l'aide de la méthode get_recordedData() du senseur.

public void refresh() throws YAPI_Exception
{
    _currentValue = _ysensor.get_currentValue();
    _max = _ysensor.get_highestValue();
    _min = _ysensor.get_lowestValue();
    _logicalName = _ysensor.get_logicalName();
    YDataSet dataset = _ysensor.get_recordedData(0, 0);
    dataset.loadMore();
    _summary = dataset.get_summary();
    int progress = 0;
    do {
        progress = dataset.loadMore();
    } while (progress < 100);
    _details = dataset.get_measures();
}


La classe WebsocketEndpoint n'a qu'un seul travail : Elle doit passer l'objet Session des connexions entrantes au WorkerThread pour qu'il appelle la méthode PreregisterHubWebsocketCallback(session) afin que les modules de cette connexion soient utilisables par l'API Yoctopuce. De la même manière à fin de la connexion, elle doit passer l'objet Session au WorkerThread pour qu'il appelle la méthode
UnregisterHubWebSocketCallback(session)
.

@ServerEndpoint("/callback")
public class WebSocketEndpoint
{
    @OnOpen
    public void onOpen(Session session)
    {
        System.out.println("WS Open " + session.getId());
        WorkerThread wt = WorkerThread.getInstance();
        wt.addSession(session);
    }

    @OnClose
    public void onClose(Session session)
    {
        System.out.println("WS Close " + session.getId());
        WorkerThread wt = WorkerThread.getInstance();
        wt.removeSession(session);
    }
}


La classe SensorCache ne présente pas grand intérêt, elle est utilisée pour stocker les données téléchargées depuis le datalogger et d'autres informations.

Comme d'habitude, le code source de l'application est disponible sur GitHub:
http://github.com/yoctopuce-examples/wsdatalogger

Conclusion


Comme nous avons pu le voir, il très simple d'utiliser le datalogger dans un callback WebSocket. La librairie fonctionne comme si elle avait elle-même établi la connexion.

Dans cette application, nous avons utilisé le datalogger pour se passer d'une base de données, mais une utilisation plus réaliste serait d'utiliser le datalogger comme solution de backup pour les cas où le serveur est en maintenance ou que la connexion internet tombe. Dans ce scénario, lors du rétablissement de la connexion, l'application pourrait récupérer les données manquantes en utilisant le datalogger.

Commenter aucun commentaire Retour au blog












Yoctopuce, get your stuff connected.