Chapter 3. Réception

La plupart des messages reçus du serveur URBI sont le résultat d'une commande précédement envoyée. Le mécanisme des tags URBI permet de lier un message à sa réponse: à chaque commande est associée un tag, et ce tag est répété dans le message de réponse. Les class UClient gèrent la réception de ces messages dans le thread indépendant créé par le constructeur, les analyses et rempli une structure UMessage. Les retours de fonction avec un tag associé peuvent être enregistrés avec la méthode registerCallback: chaque fois qu'un message avec ce tag est envoyé par le serveur, la fonction callback est appelée avec une structure UMessage comme paramètre. Les deux formes de base de registerCallback sont:

typedef UCallbackAction (*UCallback)             (const UMessage &msg);
typedef UCallbackAction (*UCustomCallback)       (void * callbackData, const UMessage &msg);

UCallbackID 	setCallback (UCallback cb, const char *tag)
UCallbackID 	setCallback (UCustomCallback cb, void *callbackData, const char *tag)

le premier paramètre est toujours un pointeur vers la fonction à appeler. callbackData est un pointeur qui sera retourné à la fonction callback chaque fois quelle est appelée. La fonction callback doit retourner URBI_CONTINUE, ou URBI_REMOVE. Dans le cas URBI_REMOVE, la fonction n'est pas enregistrée.

Quelques exemples:


UCallbackAction onImage(const UMessage &msg) {
//Test si quelqu'un a utilisé votre tag ou si un message d'erreur a été reçu
  if (msg.type != MESSAGE_DATA || ((UImage)msg).imageFormat == IMAGE_UNKNOWN)
    return URBI_CONTINUE;
  UImage img = (UImage)msg;
  msg.client.printf("Image of size (%d,%d) received from server at %d\n",img.width, img.height, msg.timestamp);

  unsigned char *image = new unsigned char[img.width*img.height*3];
  int sz = img.width*img.height*3;

  if (img.imageFormat == IMAGE_JPEG)
    convertJPEGtoRGB((const byte *) img.data, img.size, (byte *) image, sz); //provided by liburbi
  if (img.imageFormat == IMAGE_YCbCr)
    convertYCrCbtoRGB((const byte *) img.data, img.size, (byte *) image);  //provided by liburbi

  myDisplayRGBImage(image, img.width, img.height);
  delete image;
  return URBI_CONTINUE;
}

UCallbackAction onSound(const UMessage &msg) {
 if (msg.type != MESSAGE_DATA || USound(msg).soundFormat == SOUND_UNKNOWN)
    return URBI_CONTINUE;

  //convert the sound to a wav 16KHz 16bit.
  USound snd;
  snd.soundFormat = SOUND_WAV;
  snd.rate = 16000;
  snd.sampleSize = 16;
  snd.sampleFormat = SAMPLE_SIGNED;
  snd.channels = 0; //take the value from source
  snd.data = 0;
  snd.size = 0;
  convert((USound)msg, snd); //Cette fonction est fournie par liburbi
  myPlayWAV(snd.data, snd.size);
  return URBI_CONTINUE;
}

UCallbackAction onJoint(const UMessage &msg) {
  if (msg.type != MESSAGE_DATA || ((UValue)msg).type != DATA_DOUBLE)
    return URBI_CONTINUE;
  msg.client.printf("The joint value si %lf\n", UValue(msg).val);
  return URBI_CONTINUE;
}

int main(int argc, const char * argv[]) {
  UClient * cl = new UClient(argv[1]);
  if (cl->error()) urbi::exit(1); //portability call explaned below
  cl->setCallback(&onImage, "img");
  cl->setCallback(&onSound, "snd");
  cl->setCallback(&onJoint, "joint");
  cl->send("img: camera.val;");
  cl->send("loop snd: micro.val,");
  cl->send("joint: headPan.val;");
  urbi::execute();  //portability call explaned below
}

UMessage

La structure UMessage est capable de stocker les informations contenues dans nimporte quel type de message URBI grâce à un attribut "type", et à une structure UValue (union de structures de types différents). Ces deux structures sont définies comme suit:

class UMessage
{
  public:
    /// Connection dont le message est originaire
    UAbstractClient &client;
    /// timestamp coté serveur.
    int timestamp;
    /// Tag associé.
    std::string tag;

    UMessageType type;

    urbi::UValue *value;
    std::string message;
    /// Message brut sans les donné binaires
    std::string rawMessage;
};

UValue

class UValue
{
  public:
    UDataType type;
    ufloat val;  // value if of type DATA_DOUBLE
    union
    {
      std::string       *stringValue;   // value si de type DATA_STRING
      UBinary           *binary;        // value si de type DATA_BINARY
      UList             *list;          // value si de type DATA_LIST
      UObjectStruct     *object;        // value si de type DATA_OBJ
    };
}

Le champs UMessageType représentant le type peut avoir la valeur MESSAGE_SYSTEM, MESSAGE_ERROR ou MESSAGE_DATA. Si le type est MESSAGE_DATA, alors le message contient une UValue.

L'UValue contient elle meme un UDataType qui peut prendre les valeurs: DATA_DOUBLE, DATA_STRING, DATA_BINARY, DATA_LIST, DATA_OBJECT, DATA_VOID. En fonction de ce champs, les bonnes valeurs dans l'union seront assignés.

Si l'UValue est de type binaire, alors elle contient une structure UBinary défini ci après. Le champs UBinaryType dans la structure UBinary donne des informations additionnelles sur le type de la donnée (BINARY_NONE, BINARY_UNKNOWN, BINARY_IMAGE, BINARY_SOUND), et la structure appropriée (de son ou d'image) est remplie.

UBinary

class UBinary
{
  public:
  UBinaryType  type;
  union
  {
    struct
    {
       void *data; /// donnée binaire
       int  size;
    } common;
    UImage image;
    USound sound;
  };
}

USound

class USound {
 public:
 char                  *data;            // pointeur vers la données de son
 int                   size;             // taille totale en octets
 int                   channels;         // nombre de channels audio
 int                   rate;             // fréquence en Hertz
 int                   sampleSize;       // taille du sample en bit
 USoundFormat          soundFormat;      // format du son
                                         // (SOUND_RAW, SOUND_WAV, SOUND_MP3...)
 USoundSampleFormat    sampleFormat;     // format du sample
};

UImage

class UImage {
 public:
  char                  *data;            // pointeur vers l'image
  int                   size;             // taille de l'image en octets
  int                   width, height;    // taille de l'image
  UImageFormat          imageFormat;      // IMAGE_RGB, IMAGE_YCbCr, IMAGE_JPEG...
};

Des versions template de registerCallback sont aussi définies. Elles permettent de définir des callbacks sur des fonctions membres, avec 0 à 4 paramètres de tout types (incluant pointeurs et références). La seule contrainte sur la signature de fonction est qu'elle doit renvoyer un UCallbackAction, et prendre un const UMessage& comme dernier paramètre. Quelques exemples:

class Test {
   public:
     UCallbackAction onJoint(int value, const UMessage &msg);
}:

UCallbackAction  Test::onJoint(int value, const UMessage &msg) {
  msg.client.printf("got a message at %d with tag %s, our int is %d\n",msg.timestamp, msg.tag, value);
  return URBI_REMOVE;  //unregister ourself
}

int main(int argc, const char * argv[]) {
  Test *a = new Test();
  UClient * cl= new UClient(argv[1]);
  if (cl->error()) urbi::exit(1);
  cl->setCallback(*a, &Test::onJoint, 12, "tag");
  cl->send("tag: headPan.val;");
  urbi::execute();
}