Chapitre 9. Créer des composants: l'architecture UObject

Table des matières

UObject
Les bases
Ajouter des attributs
Lier une fonction ou un évenement
Minuteurs
Types binaires avancés
L'attribut load
L'attribut remote
L'exemple de colormap
En pratique, comment profiter d'un UObject?
Comment installer le kit de développement pour construire/lier des composants pour votre robot ?
Comment créer et utiliser un composant distant ou intégré ?
Comment incorporer plusieurs composants dans votre serveur URBI ?
Comment distribuer l'un de vos composants et le proposer à d'autres ?

L'architecture UObject est la façon la plus avancée d'étendre les possibilités d'URBI et d'intégrer de puissants composants au langage. Elle est pour l'instant limitée au C++ mais devrait se généraliser aux autres langages à l'avenir. L'idée est de prendre une classe C++ et, après quelques petites modifications, d'être capable de l'incorporer dans URBI de manière à ce que ses méthodes et ses attributs soient accessibles comme s'il s'agissait d'une classe URBI pure. Un peu de terminologie: l'architecture UObject permet d'ajouter un composant au langage, et ce composant sera vu comme un objet.

Il y a actuellement deux manières d'intégrer votre classe C++ dans URBI:

  • Le mode plugin: vous pouvez incorporer l'objet directement dans URBI (le lier à l'URBI Engine) et il fera alors partie du code binaire de l'URBI Engine.

  • Le mode remote: vous pouvez sinon l'exécuter en tant processus distant qui se connectera à votre URBI Engine et ajoutera de façon transparente l'objet au langage, tout comme dans le mode plugin, mais de loin.

Dans les deux cas, nous vous fournissons les outils nécessaires à ce lien. La bonne nouvelle est que le code source C++ de votre objet est exactement le même dans les deux cas, et la façon dont vous l'utilisez est également transparente. Vous pouvez donc choisir d'incorporer n'importe quel composant, à volonté (à l'avenir, il sera même possible de gérer la liaison à chaud).

Nous allons maintenant voir comment transformer votre classe C++ en une classe UObject, et comment lier les méthodes et attributs de votre classe avec URBI.

UObject

Les bases

Créons un objet colormap, composé du fichier colormap.cpp et du fichier colormap.h. Le fichier colormap.h devrait débuter ainsi:

#include <uobject.h>
using namespace urbi;

class colormap : public UObject
{
  public:
  colormap(string);
  ...
};

Votre constructeur doit être renommé init. Le constructeur par défaut lenomdemaclasse(string) qui est adapté aux UObjects doit être utilisé à la place. Par exemple, définissons le constructeur init qui prend un point RGB (rouge/vert/bleu) comme définition colorimétrique:

public:
  colormap(string);
  int init (int r, int g, int b);
  ...

Pour l'instant, c'est tout se dont vous avez besoin du côté définition de classes. Passons au code de colormap.cpp:

#include "colormap.h"

UStart(colormap);

colormap::colormap(string s) : UObject(s)
{
  UBindFunction(colormap, init);
}

int colormap::init(int r, int g, int b)
{
}

...

Deux nouvelles choses ici: vous devez invoquer la ligne "magique" UStart(monobjet) afin que le system en prenne connaissance. Ensuite, vous devez vous assurer que le constructeur par défaut appelle bien le constructeur UObject, passe la chaîne de caractères (string), lie la fonction init pour la rendre visible et l'exporter vers URBI. Cette étape est nécessaire si vous voulez que le constructeur init soit appelé par URBI à la création d'un objet. La méthode init doit retourner 0 en cas de succès, et tout autre nombre en cas d'échec (vous pouvez aussi retourner void qui est considéré comme un succès).

Il n'y a rien de plus à savoir. A ce stade, vous disposez déjà d'un objet exportable appelé colormap avec une méthode init. Maintenant, vous pouvez le compiler et obtenir le code binaire, prêt à être lié.

Supposons que vous ayez lié le code à l'URBI Engine, pour en faire un composant en mode plugin (nous verrons comment un peu plus tard). Maintenant, comment exploiter ce nouvel objet colormap ? Hé bien, il n'y a rien à faire de plus: tout est là. Rappelez-vous qu'en URBI il n'existe aucune différence entre une classe et une instance (langage prototypé), donc définir colormap est suffisant pour obtenir un objet colormap fonctionnel. Vous pouvez l'évaluer et observer le résultat:

colormap;
[139464:notag] OBJ [load:1.000000]

NB: Par défaut, il existe un attribut load exporté, ignorons-le pour le moment.

Définissons une sous-classe de colormap. Cette action appelera le constructeur init côté C++ et génerera une nouvelle instance de la classe C++ colormap mais, bien entendu, tout est fait automatiquement et vous n'avez pas à vous en préoccuper:

ball = new colormap(123,45,12);
ball;
[139464:notag] OBJ [load:1.000000]

Vous pouvez constater que la syntaxe de création de l'objet est identique à celle de C++. Dans la mesure du possible, nous nous sommes efforcés de conserver la syntaxe bien connue du C/C++ dans URBI, car nous considérons qu'il n'y a pas de temps à perdre à apprendre des choses que nous connaissons déjà (du moment qu'il n'y ait pas de confusion en terme de sémantique).

Ajouter des attributs

Notre objet colormap n'est pour l'instant pas vraiment enthousiasmant. Pour le rendre plus utile, ajoutons-lui des attributs et rendons-les disponibles dans URBI. Pour ajouter une variable x, nous allons tout simplement ajouter UVar x; à l'intérieur de la définition de la classe:

#include <uobject.h>
using namespace urbi;

class colormap : public UObject
{
public:
  colormap(string);

  UVar x; // définition de la variable exportée
  ...
};

et ajouter le code de liaison dans la méthode init:

int colormap::init(int r, int g, int b)
{
  UBindVar(colormap, x);
  ...
}

En réalité, vous pouvez mettre votre code de liaison (UBindVar) où vous voulez, en particulier il peut être dans le constructeur d'objet C++ ou dans la méthode init. Si vous le placez dans le constructeur C++, la variable sera disponible pour l'instance de base (celle qui est là dès le début et qui ne nécessite pas d'être crée par quelconque new), ou si vous le placez dans la méthode init, seuls les autres objets créés avec new auront cette variable. Cela peut être utile si vous ne comptez pas utiliser l'instance de base dans le cas où vous souhaitez plutôt la dériver pour la spécifier. Dans ce cas, placez toutes vos liaisons uniquement dans la méthode init et l'instance de base ne sera alors qu'une instance fantôme. Sachez que UObject::derived est un booléen qui vous dit si la classe a été dérivée avec new ou s'il s'agit de la classe de base.

Vous pourrez vérifier: maintenant colormap.x et ball.x seront là.

Pour affecter une valeur à x depuis l'intérieur de votre classe C++, utilisez-là simplement comme une variable normale, UObject fera le reste:

x = 42;
  ou
x= "hello";

L'opérateur = de C++ a été redéfini pour UVar, il n'y a donc pas d'inquiétude à avoir et vous pouvez donc affecter des valeurs à x comme vous le feriez dans URBI.

Désormais, comment lire la variable ? Là encore nous nous sommes efforcés de rendre les choses simples: vous pouvez employer un "casting" à la C pour obtenir la valeur dans le type C++ approprié. Attention cependant car il n'y a, pour le moment, aucune exception de levée en cas d'erreur:

x = 42;
printf("Valeur de x: %d\n",(int)x);

x est ce que l'on appelle un "hook" sur la variable URBI colormap.x. Vous pouvez aussi définir des hook sur n'importe quelle variable en définissant votre propre instance UVar où que vous vouliez (la liaison sera automatique, nul besoin de UBindVar, le constructeur UVar se charge de tout). Voici quelques exemples:

UVar("camera.val");
UVar("camera","val");
UVar* myvar = new UVar("headPan","val");

La raison pour laquelle vous devez appeler UBindVar pour une UVar définie dans le corps de votre classe est que cette UVar n'est pas allouée dynamiquement. Une telle UVar ne connaît pas encore son nom et la macro UBindVar lui dit simplement qui elle est. Cette étape n'est pas nécessaire avec le constructeur UVar(string) qui prend comme nom son argument.

Bien entendu, votre objet C++ peut contenir plusieurs attributs qui ne seront pas exportés vers URBI et resteront "privés" au niveau de la classe C++. Pour rendre donc un attribut disponible à URBI, vous devez le définir comme une UVar ou réaliser un UBindVar sur une UVar de la définition de votre objet.

Il est parfois intéressant de surveiller les accès ou les modifications apportées à une variable. Pour cela, on affecte une fonction-callback à la variable, en indiquant si la fonction doit être appelée à l'accès ou à la modification de la dite variable:

UNotifyChange(x,&colormap::moncallback);
UNotifyAccess(UVar("doo.daa",&colormap::monautrecallback);
UNotifyChange("uneautre.variable",&colomap::unautrecallback);

Notifier à la modification (UNotifyChange) signifie que le callback est appelé à chaque fois que la variable est modifié du côté d'URBI (pour les variables liées à des capteurs, à chaque fois que la valeur du capteur est mise à jour). Notifier à l'accès signifie que le callback sera appelé à chaque fois que quelqu'un évaluera la variable côté URBI, vous permettant de mettre à jour la valeur avant l'évaluation. Dans ce cas, il est conseillé de coder un mécanisme de cache dans votre callback si la variable est destinée à être évaluée fréquemment.

Ces lignes Notify seront généralement placées dans la fonction init ou dans le constructeur de votre objet, le choix entre l'un et l'autre étant dicté par les mêmes raisons que pour UBindVar. Remarquez que vous devez passer un pointeur à l'une des méthodes de votre objet. Il existe deux façons de prototyper pour ce genre de callback:

UReturn moncallback();
UReturn moncallback(UVar&);

La première est la plus simple: la fonction est appelée lorsque la condition est remplie. La seconde fait la même chose mais passe en plus l'UVar en paramètre. Ainsi vous pouvez utiliser le même callback avec plusieurs variables et obtenir celle qui est en rapport avec l'appel courant.

Lier une fonction ou un évenement

Tout comme avec les attributs, vous pouvez relier une fonction à l'objet côté URBI. Il n'y a rien de spécial à faire, seulement respecter la construction suivante:

int colormap::init(int r, int g, int b)
{
  UBindFunction(colormap, faisquelquechose);
  ...
}

std::string colormap::faisquelquechose(int, float)
{
  ...
}

Cela rendra la méthode faisquelquechose visible de l'extérieur. Ne vous préoccuppez pas des paramètres, ils seront reconnus et exportés pour vous. Pour l'instant, il n'est cependant pas possible de surcharger une fonction avec ce mécanisme (et notamment, vous ne pouvez surcharger le constructeur init).

De la même façon, vous pouvez relier un évenement à l'une des méthodes de votre objet. Ainsi cette méthode sera appelée à chaque fois que l'évenement correspondant sera émis côté URBI, et vous obtiendrez les paramètres par la même occasion. Pour cela, faîtes:

UBindEvent(colormap, reagisacela);

Vous pouvez également demander à être informé de la fin d'un évenement (comme vous le savez, un évenement peut durer un certain temps en URBI). Par exemple, si vous désirez être notifié en appelant la méthode cestlafin de votre objet, faîtes:

UBindEvent(colormap, reagisacela);
UBindEventEnd(colormap, reagisacela, cestlafin);

cestlafin doit être prototypé comme ci:

void colormap::endthis();

Minuteurs

Vous pouvez facilement placer des minuteurs (timers) qui appeleront votre fonction à intervalle régulier. La syntaxe est la suivante:

USetTimer(temps_en_ms, &monobjet::moncallback);

moncallback étant une méthode de votre objet avec le prototype suivant:

UReturn monobjet::moncallback();

Vous ne pouvez pas utiliser une fonction venant d'un autre objet.

Types binaires avancés

L'affectation des entiers, des réels et des chaînes de caractères ainsi que leur lecture par "cast" d'UVar sont simples. Pour une donnée binaire comme une image ou un son, vous aurez besoin des types UImage et USound. Voici en citation leur définition dans le fichier uobject.h:


   ///Class encapsulating an image.
   class UImage {
     public:
       char                  *data;            ///< pointer to image data
       int                   size;             ///< image size in byte
       int                   width, height;    ///< size of the image
       UImageFormat          imageFormat;
   };

   ///Class encapsulating sound informations.
   class USound {
     public:
       char                  *data;            ///< pointer to sound data
       int                   size;             ///< total size in byte
       int                   channels;         ///< number of audio channels
       int                   rate;             ///< rate in Hertz
       int                   sampleSize;       ///< sample size in bit
       USoundFormat          soundFormat;      ///< format of the sound data
       USoundSampleFormat    sampleFormat;     ///< sample format
   }

Vous les reconnaissez: ce sont les types utilisés dans la liburbi. Si votre UVar est une image, comme camera.raw, vous pouvez simplement la "caster" en une UImage et les différents attributs seront renseignés, en particulier le contenu binaire sera dans data et la taille dans size. Idem pour un son.

Méfiez-vous de camera.val: il se peut que ce soit un binaire compressé avec la méthode JPEG. Dans ce cas, vous devrez le convertir avec l'une de ces fonctions, décrites à la section Fonctions de conversion:

 int convertRGBtoYCrCb (const byte* source, int sourcelen, byte* dest);
 int convertYCrCbtoRGB (const byte* source, int sourcelen, byte* dest);
 int convertJPEGtoYCrCb(const byte* source, int sourcelen, byte* dest, int &size);
 int convertJPEGtoRGB  (const byte* source, int sourcelen, byte* dest, int &size);

Si vous souhaitez affecter un son, disons à speaker.val, remplissez une variable USound et affectez-la à l'UVar appropriée, l'opérateur = a été redéfini pour gérer cela. Cependant, nous ne prenons en charge pour l'instant que le format WAV.

L'attribut load

Nous avons déjà mentionné l'attribut load, défini comme une UVar liée par défaut dans UObject. Cet attribut peut être utilisé pour tester, dans votre code C++, si l'objet est activé ou non du côté d'URBI. Dans URBI, un appel à monobjet on; mettra load à 1 et un appel à monobjet off; le mettra à 0. Vous pouvez donc facilement tester dans différentes fonctions si vous avez à réaliser les calculs ou non, en vous basant sur la valeur de load.

Cela s'avère très utile si vous voulez être en mesure d'activer/désactiver un calcul lourd pour le processeur qui pourrait tourner pour rien en tâche de fond. Par exemple, vous pouvez éteindre la détection de balle de l'Aibo avec:

ball off;

Notez qu'il s'agit d'une construction diffusable: si vous faites on/off sur un groupe, cela se répercutera récursivement sur chaque membre du groupe. C'est exactement ce qui se passe avec la commande motors on;.

NB: Vous disposez également de monobjet switch; pour basculer entre on et off.

L'attribut remote

Dans la définition d'UObject, l'attribut remote est là pour savoir si votre objet tourne en tant que composant distant (remote) ou en tant qu'intégré (plugin). Cela peut s'avérer utile si vous souhaitez vous comportez différemment dans les deux cas, classiquement lors du tranfert d'un important volume de données, que l'on peut effectuer avec ou sans compression. colormap met à profit cet attribut.

L'exemple de colormap

Voici un exemple dans la pratique d'un objet colormap tel qu'il est utilisé dans Aibo pour calculer la position approximative d'un blob de couleur, définie par une sous-plage de l'ensemble de la plage de couleurs YCrCb. Vous allez pouvoir constater la liaison du callback avec la source, habituellement la caméra. Le callback est réglé sur l'attribut .val ou .raw de l'objet source, en fonction du status de l'objet, distant ou non. En mode distant, nous souhaitons utiliser la compression JPEG et travailler avec l'image en résultant, alors qu'en mode intégré, on peut utiliser de la mémoire partagée sur le tampon raw pour obtenir une meileurre image dépourvue d'artefact et ainsi éviter de compresser/décompresser pour rien.

Vous verrez également comme les affectations aux attributs x et y ainsi que les autres attributs décrivant la forme du blob sont simples:

First the colormap.h file (extracts only):

 #include <uobject.h>
 using namespace urbi;

 class colormap : public UObject
 {
   public:

     colormap(string);
     int init(string,int,int,int,int,int,int,ufloat);

     UVar   x;
     UVar   y;
     UVar   visible;
     UVar   ratio;
     UVar   threshold;
     UVar   orientation;
     UVar   elongation;
     UVar   ymin, ymax, cbmin, cbmax, crmin, crmax;

     UReturn newImage(UVar&);
 };

Ici nous utilisons ufloat au lieu de float car il est adapté aussi bien au 32bits, qu'au 64bits, voire même aux cartes-mères dépourvues d'unité de calcul à virgule flottante. Il est donc préférable pour les applications embarquées.

Maintenant, voici le code principal:

 #include "colormap.h"

 UStart(colormap);

 //! colormap constructor.
 colormap::colormap(string s) :
   UObject(s)
 {
   UBindFunction(colormap,init);
 }

 //! colormap init function
 int
 colormap::init(string source,
                int _Ymin,
                int _Ymax,
                int _Cbmin,
                int _Cbmax,
                int _Crmin,
                int _Crmax,
                ufloat _threshold)
 {
   UBindVar(colormap,x);
   UBindVar(colormap,y);
   UBindVar(colormap,visible);
   UBindVar(colormap,ratio);
   UBindVar(colormap,threshold);
   UBindVar(colormap,orientation);
   UBindVar(colormap,elongation);
   UBindVar(colormap,ymin);
   UBindVar(colormap,ymax);
   UBindVar(colormap,cbmin);
   UBindVar(colormap,cbmax);
   UBindVar(colormap,crmin);
   UBindVar(colormap,crmax);

   if (remote)
     UNotifyChange(source+".val",&colormap::newImage);
   else
     UNotifyChange(source+".raw",&colormap::newImage);




   // initialization
   ymin        = _Ymin;
   ymax        = _Ymax;
   cbmin       = _Cbmin;
   cbmax       = _Cbmax;
   crmin       = _Crmin;
   crmax       = _Crmax;
   threshold   = _threshold;
   x           = -1;
   y           = -1;
   visible     = 0;
   orientation = 0;
   elongation  = 0;
   ratio       = 0;

   return 0;
 }

 //! colormap image update
 UReturn
 colormap::newImage(UVar& img)
 {
   if ((ufloat)load < 0.5) return(1);

   UImage img1 = (UImage)img;  //ptr copy

   if (remote)
     convertYCrCb(img1); // this function is available in UObject 1.0 only

   int w = img1.width;
   int h = img1.height;

   //lets cache things
   int ymax = this->ymax; int ymin = this->ymin;
   int crmin = this->crmin; int crmax = this->crmax;
   int cbmin = this->cbmin; int cbmax = this->cbmax;

   long long x=0,y=0,xx=0,yy=0,xy=0;
   int size = 0;
   for (int i=0;i<w;i++)
     for (int j=0;j<h;j++) {

       unsigned char lum = img1.data[(i+j*w)*3];
       unsigned char cb  = img1.data[(i+j*w)*3+1];
       unsigned char cr  = img1.data[(i+j*w)*3+2];




       if ( (lum  >= ymin) &&
            (lum  <= ymax) &&
            (cb >= cbmin) &&
            (cb <= cbmax) &&
            (cr >= crmin) &&
            (cr <= crmax) ) {
         size++;
         x += i;
         y += j;
         xx += i*i;
         yy += j*j;
         xy += i*j;
       }
     }

   this->ratio = ((ufloat)size)/((ufloat)(w*h));
   if (size > (int)((ufloat)threshold * (ufloat)(w*h))) {

     this->visible = 1;
     this->x = 0.5 - ((double)x /
         ((double)size * (double)w));
     this->y = 0.5 - ((double)y /
         ((double)size * (double)h));

     //orientation: first eighenvector of covariance matrice
     double m00 = (double)xx - (double)(x*x)/(double)(size);
     double m11 = (double)yy - (double)(y*y)/(double)(size);
     double m01 = (double)xy - (double)(x*y)/(double)(size);

     //bigest eighenvalue
     double l = (m00+m11)/2.0 + 0.5*sqrt((m00+m11)*
                (m00+m11)-4*(m00*m11-m01*m01));

     //first eighenvector orientation
     double angle = atan2(l-m00, m01);
     this->orientation = angle* 180.0 /M_PI;

     //variance on new axis => elongation
     double angle2 = angle + M_PI/2.0;
     double X = x*cos(angle)+y*sin(angle);
     double Y = x*cos(angle2)+y*sin(angle2);
     double XX = xx*cos(angle)*cos(angle)+yy*sin(angle)*sin(angle)+
                 2.0*xy*cos(angle)*sin(angle);
     double YY = xx*cos(angle2)*cos(angle2)+yy*sin(angle2)*sin(angle2)+
                 2.0*xy*cos(angle2)*sin(angle2);
     double vX = XX - X*X/(double)size;



     double vY = YY - Y*Y/(double)size;

     this->elongation = sqrt(vX/vY);

   }
   else {
     this->x=-1;
     this->y=-1;
     this->visible = 0;
   }

   return(1);
 }

L'objet colormap est alors incorporé dans l'URBI Engine et il est employé pour créer un détecteur de balle depuis le fichier URBI.INI:

ball = new colormap("camera",0,255,120,190,150,230,0.0015);

En pratique, comment profiter d'un UObject?

Nous mettons à disposition une série de scripts qui ont pour but de vous aider à construire un composant distant ou un composant intégré le plus facilement possible. Sous Linux, tout cela est fait par un fichier Makefile que vous devez placer dans le répertoire où se trouve le code source de votre UObject. Sous Windows, bibliothèques et projets seront fournis pour les compilateurs les plus courants, mais pour l'instant Mingw et l'environnement Cygwin doivent être utilisés. Dans ce qui suit, nous supposons que vous utilisez un environnement de la famille Unix.

Comment installer le kit de développement pour construire/lier des composants pour votre robot ?

Pour construire ou utiliser des composants pour un serveur URBI donné, vous devez installer le kit de développement (SDK) correspondant à ce serveur. Téléchargez-le sur le site Web d'URBI (ou depuis le site Web du constructeur de votre robot), décompressez l'archive, et, pour l'installer, taper en tant qu'administrateur (root):

make install

Comment créer et utiliser un composant distant ou intégré ?

Les composants distants et intégrés sont construits de la même façon. Copiez le fichier Makefile.module que vous trouverez dans /usr/local/share/urbicore dans le répertoire de votre composant et renommez-le en Makefile. Son comportement par défaut est de compiler toutes les sources du répertoire (éditer-le si vous avez besoin d'ajouter des actions supplémentaires ou de modifier les options de compilation).

Pour construire votre composant et le lier à un serveur particulier, tapez:

make TARGET=<cible> link

En remplaçant <cible> par la cible correspondant à votre serveur (en l'occurrence: aibo). Cette commande produira un nouveau serveur URBI avec votre composant intégré à lui. Remplacez l'ancien serveur avec celui-ci (le fichier URBI.BIN pour Aibo) et voilà !

Pour construire votre composant et en faire un composant distant, tapez:

make TARGET=remote link

Cela produira un exécutable qui prendra comme unique argument le nom d'hôte du robot ou son adresse IP.

Méfiez-vous: certaines cibles possèdent des dépendances supplémentaires, comme pour aibo par exemple qui nécessite le OPEN-R SDK. En d'autres termes, il vous faut installer le kit de développement de Sony avant de pouvoir construire des composants intégrés pour Aibo.

Comment incorporer plusieurs composants dans votre serveur URBI ?

Pour incorporer plusieurs composants dans votre serveur URBI, construisez-les comme expliqué ci-dessus si vous avez leurs sources, en omettant le link à la fin de la commande:

make TARGET=<target>

Cela va créer un ensemble de bibliothèques prêtes à lier. Tapez alors dans l'un des répertoires-sources:

make TARGET=<target> link LIBS=<libs> LIB_DIR=<libdir>

libs est le nom de toutes les bibliothèques que vous souhaitez lier, et libdir le chemin de ces bibliothèques. Par exemple, si vous possédez un Aibo, que vous réalisez un composant monopoly dans le répertoire courant, et que vous désirez l'intégrer avec le composant detecteargent que vous avez téléchargé dans /home/moi/recus en tant que libdetectmoney.a, tapez:

make TARGET=aibo link LIBS=detecteargent LIB_DIR=/home/moi/recus

Comment distribuer l'un de vos composants et le proposer à d'autres ?

Vous pouvez distribuer soit les sources de votre composant, soit la bibliothèque (.a) générée par le processus de construction comme décrit précédemment (make TARGET=<target>). On pourra alors lier votre composant à un serveur URBI comme expliqué précédemment. Notez bien que la bibliothèque est dépendante de l'architecture: un composant compilé pour Aibo ne peut être lié en tant que module distant, les sources doivent être recompilées. Nous vous recommandons fortement de publier à la fois votre code source pour d'éventuels recompilations si votre licence le permet ET une ou plusieurs versions binaires pour les personnes désireuses de seulement lier votre composant et l'utiliser comme composant distant.

Le site Web http://www.urbiforge.com est une plateforme pour échanger les composants et les scripts URBI. Vous pouvez y envoyer votre travail afin que la communauté en profite.