Contrôle à distance par callback WebSocket

Contrôle à distance par callback WebSocket

websocket_iconJusqu'à présent, il y avait deux façon d'accéder aux modules Yoctopuce à travers le réseau: soit par une connection directe sur un réseau local, soit en utilisant la méthode du Callback HTTP qui permet de piloter un module Yoctopuce à distance, même lorsqu'il est protégé par un filtre NAT. Mais cette méthode par Callback HTTP a quelques limitations. Aujourd'hui, nous vous présentons une nouvelle méthode de connection, appelée Callback Websocket qui amène toutes les possibilités d'une connection directe dans le scénario du callback derrière un filtre NAT.

HTTP et WebSockets


Lors d'une requête HTTP usuelle, le flux d'information est extrêmement simple: le client envoie une requête, et écoute la réponse du serveur. Il ne s'agit pas à proprement parler d'une conversation, mais simplement d'une réponse à une question. Le client HTTP ne peut pas répondre à ce que le serveur lui a dit sans recommencer une nouvelle communication séparée.

Il y a néanmoins dans le standard HTTP 1.1 une porte ouverte vers une amélioration: le client peut demander d'upgrader le protocole de communication. Une méthode d'upgrade qui s'est standardisée s'appelle les WebSockets, et est définie dans le RFC 6455. Cette upgrade transforme le simple canal question/réponse en un lien bidirectionnel permettant d'échanger des messages quelconques dans les deux directions.

Cette transformation de la connection HTTP exige que les deux parties en soient capables. Dès maintenant, les modules réseau de Yoctopuce, le VirtualHub et nos librairies de programmation en sont capables. Mais pour pouvoir transformer un callback HTTP en callback WebSocket, vous aurez aussi besoin d'un serveur Web basé sur une technologie qui permet l'upgrade de connection. C'est le cas par exemple de Java et Node.JS. Par contre, les implémentations classiques de PHP sur Apache n'en sont à notre connaissance pas capable.

Avantages des WebSockets


L'utilisation de WebSockets permet d'accéder à plusieurs fonctionnalités avancées des librairies Yoctopuce qui n'étaient pour l'instant pas disponibles via un Callback HTTP:

  • un callback WebSocket peut énumérer et récupérer des données stockées dans l'enregistreur de données intégré dans chaque senseur Yoctopuce.
  • un callback WebSocket peut utiliser les fonctions de communication bidirectionnelles des modules séries, par exemple pour exécuter des requêtes MODBUS avec un Yocto-RS485.
  • un callback WebSocket peut profiter des notifications instantanées de changement de valeur par callback, et des notifications périodiques de valeurs moyennées des capteurs.
  • un callback WebSocket peut garder une connection persistante entre le hub et le serveur, par exemple pour implémenter une interaction avec un utilisateur.
  • un callback WebSocket peut même être utilisé pour effectuer une mise à jours de firmware.

Exemple en Java


La librairie Yoctopuce utilise l'API standard de Java pour les WebSockets (JSR-356). Il existe d'autres frameworks de plus haut niveau pour utiliser les WebSockets en Java, mais JSR-356 est le standard le plus largement disponible. En particulier, il est disponible sur le serveur TomCat dès la version 7.

Pour utiliser la librairie Yoctopuce pour Java avec les WebSockets, vous aurez besoin d'inclure JavaEE dans votre projet.


import com.yoctopuce.YoctoAPI.YAPIContext;
import com.yoctopuce.YoctoAPI.YAPI_Exception;
import com.yoctopuce.YoctoAPI.YModule;
import com.yoctopuce.YoctoAPI.YRelay;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/wscallback")
public class YServerWebsocket
{

  @OnOpen
  public void onOpen(final Session session)
  {
    // log onOpen for debug purpose
    System.out.println(session.getId() + " has open a connection");

    Thread thread = new Thread(new Runnable()
    {
      @Override
      public void run()
      {

        // since all connection use the same process create a private context
        YAPIContext yctx = new YAPIContext();
        try {
          // register the YoctoHub/VirtualHub that start the connection
          yctx.RegisterHubWebSocketCallback(session);

          // list all devices connected on this hub (only for debug propose)
          System.out.println("Device list:");
          YModule module = YModule.FirstModuleInContext(yctx);
          while (module != null) {
            System.out.println("   " + module.get_serialNumber()
              + " (" + module.get_productName() + ")");
            module = module.nextModule();
          }

          // play a bit with relay output :-)
          try {
              YRelay relay = YRelay.FirstRelayInContext(yctx);
              if (relay != null) {
                relay.set_state(YRelay.STATE_A);
                Thread.sleep(500);
                relay.set_state(YRelay.STATE_B);
                Thread.sleep(250);
                relay.set_state(YRelay.STATE_A);
                Thread.sleep(250);
                relay.set_state(YRelay.STATE_B);
                Thread.sleep(500);
                relay.set_state(YRelay.STATE_A);
                Thread.sleep(1000);
                relay.set_state(YRelay.STATE_B);
                Thread.sleep(500);
                relay.set_state(YRelay.STATE_A);
                Thread.sleep(1000);
                relay.set_state(YRelay.STATE_B);
              } else {
                  System.out.println("No Relay connected");
              }

            } catch (InterruptedException ignore) {}

        } catch (YAPI_Exception ex) {
          System.out.println(" error (" + ex.getLocalizedMessage() + ")");
          ex.printStackTrace();
        }
        // no not forget to FreeAPI to ensure that all pending operation
        // are finished and freed
        yctx.FreeAPI();
      }
    });
    thread.start();
  }
}
 


Vous constaterez une petite différence dans ce code par rapport à l'utilisation habituelle de notre API. Lors de l'énumération des modules connectés, plutôt que d'appeler FirstRelay, nous utilisons FirstRelayInContext, en passant un objet nouvellement créé de la classe YAPIContext comme paramètre. En effet, comme il est désormais possible d'avoir plusieurs communications persistantes avec plusieurs YoctoHubs, il est utile de pouvoir contrôler à quel hub l'on désire s'adresser: soit en les isolant chacun dans leur propre contexte, soit en utilisant un contexte partagé de sorte à pouvoir accéder à différents hubs connectés simultanément.

Exemple en Node.js


Nous avons aussi écrit un exemple similaire avec la nouvelle librairie EcmaScript 2015, que nous vous présentions la semaine dernière. En Node.js, nous avons choisi de nous appuyer sur la librairie WebSocket appelée ws, parce que c'est celle qui est la plus utilisée et qu'elle n'a quasiment aucune dépendance externe. Vous pouvez l'utiliser avec votre moteur de serveur HTTP préféré. Nous avons codé cet exemple en utilisant un simple WebSocketServer, mais ws fonctionne aussi par avec Express par exemple.


import { YAPI, YAPIContext, YFunction, YModule, YErrorMsg } from 'lib/yocto_api';

var WebSocketServer = System._nodeRequire('ws').Server;

async function WebSocketCallbackHandler(ws)
{
    console.log('Received a WebSocket request');

    // Code below starts the Yoctopuce library in WebSocket Callback mode
    // and interact with modules connected on the hub that made the callback
    let errmsg = new YErrorMsg();
    let yctx = new YAPIContext();
    if(await yctx.RegisterHubWebSocketCallback(ws, errmsg) != yapi.SUCCESS) {
        console.log('HTTP callback error: '+errmsg);
        yctx.FreeAPI();
        return;
    }

    // Display a list of modules on incoming hub to the Node.js console
    await yapi.UpdateDeviceList(errmsg);
    var module = YModule.FirstModuleInContext(yctx);
    while(module) {
        let msg = await module.get_serialNumber();
        msg += ' (' + (await module.get_productName()) + ')';
        console.log(msg);
        module = module.nextModule();
    }
    yctx.FreeAPI();
}

// Instantiate a simple HTTP server
var wss = new WebSocketServer({ port: 8044 });
wss.on('connection', WebSocketCallbackHandler);

console.log('Node.js WebSocket Callback server running at http://127.0.0.1:8044/');
 


Comme en Java, nous pouvons maintenant créer un objet YAPIContext pour chaque connection. Cela nous évite de mélanger les ressources de tous les hubs, comme c'était le cas avec notre ancienne librairie Node.JS. Pour lancer cet exemple, il suffit de lancer la ligne de commande suivante à la racine de la nouvelle librairie:


jspm run example_nodejs/Prog-WebSocketCallback/demo
 



Utilisation des WebSockets en connection directe


L'utilisation de WebSockets n'est pas limitée aux callbacks pour passer à travers un filtre NAT. Il est aussi possible d'utiliser les WebSockets pour une connection directe sur un réseau local. Par exemple, vous pouvez utiliser:


YAPI.RegisterHub("ws://192.168.1.177",errmsg);
 


Ce changement est sans incidence pour le reste du code, et toutes les fonctions de la librairie Yoctopuce se comporteront comme si de rien était. Mais l'avantage de cette technique est que la librairie utilisera alors une connection TCP persistante pour toutes les requêtes, à la place de connections HTTP individuelles pour chaque requête. Cela supprime la latence introduite par l'établissement des connections TCP sur les réseaux plus lents comme les connections WiFi, et en particulier pour les connections par VPN sur un lien GSM.

L'utilisation de WebSockets en connection directe est déjà disponible en démonstration pour Java et EcmaScript, mais sera officiellement disponible dans tous les langages de programmation à la fin du mois de Janvier.

Téléchargements


Vous pouvez essayer dès à présent notre nouvelle API WebSocket en Java et JavaScript. Voici les liens vers le code de démonstration:

N'utilisez pas ce code en production pour l'instant. Nous publierons une version officielle à la fin du mois de janvier, lorsque le code sera optimisé et testé. Mais cette mise ne bouche devrait vous aider à vous y préparer...

Commenter aucun commentaire
Retour au blog












Yoctopuce, get your stuff connected.