Table of Contents
The UObject architecture is the most advanced way to extend URBI and integrates powerful components in the language. It's currently limited to C++ but should generalize to other languages in the future. The idea is to take a C++ class and, after a few small modifications, to be able to plug this class in the URBI language so that one can access its methods and attributes as if they were pure URBI objects. A few words about terminology: the UObject architecture enables to add a component to the language, and this component will be seen as an object.
There are actually two ways of integrating your C++ class inside URBI:
Mode plugin: You can plug the object directly in URBI (link it to the URBI Engine) and it will be part of the binary code of the URBI Engine.
Mode remote: You can run it as an autonomous remote process that will connect itself to your URBI Engine and transparently add the object to the language, just like in the plugin mode, but remotely.
In both cases, we provide the necessary tools to make the link (described below). The good news is that the C++ source code of your object is exactly the same in both cases, and the way you use it inside URBI is also transparent. So, you can decide to plug/remote-run a component at will (hopefully in the future, you will be able to relocate the object at runtime, but not for now).
We will now see how to turn your C++ class into an UObject class, and we will see then how to connect the methods and attributes of the C++ class to URBI.
Let's create a colormap object, composed of colormap.cpp and colormap.h. The colormap.h should start like this:
#include <uobject.h>
using namespace urbi;
class colormap : public UObject
{
public:
colormap(string);
...
};
Whatever constructor you previously had should be renamed init. The default constructor myclassname(string) which is appropriate for UObjects must be used instead. For example, you might define the constructor init which takes a RGB point as a color definition like this:
public: colormap(string); int init (int r, int g, int b); ...
For the moment, that's all what you need on the class definition side. Let's have a look at the main code in 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)
{
}
...
Two new things here: you have to invoke the "magic" line UStart(myobject) in order to let the system know about it. Then, you must make sure that the default constructor calls the UObject constructor and passes the string, and also bind the init function to make it visible and export it in URBI. This is required if you want the init constructor to be called by URBI upon a new object creation. The init method should return 0 upon success, anything else in case of failure (you can also return void which is considered as a success).
There is nothing else to know, at this stage you already have a exportable object called 'colormap' with a method 'init'. Now, you can compile it and get a binary code ready to link.
Let's assume than you have linked this code to the URBI Engine, to make it a component in plugin mode (we will see how later). Now, how to use this new colormap object? Well, not much has to be done: it's already there. Remember that in URBI there is no difference between a class and an instance (prototype-based language), so defining colormap is enough to have a functionnal colormap object. You can try to evaluate it to see this:
colormap; [139464:notag] OBJ [load:1.000000]
NB: By default, there is an exported load attribute in UObject, let's ignore it for the moment.
Let's define a subclass of colormap. This action will call the init constructor on the C++ side and spawn a new instance of the C++ colormap class, but of course this is all done automatically and you don't have to take care of that:
ball = new colormap(123,45,12); ball; [139464:notag] OBJ [load:1.000000]
You see that the syntax to create a new object in URBI is identical to the C++ syntax. Each time is was possible, we have kept the familiar C/C++ syntax in URBI, because there is no point to waste time learning stuffs we already know (as long as there is no confusion in term of semantics).
Our colormap object is not much fun so far. To make it more useful, we can start to add attributes to the object and bind them to URBI. To add a x variable, we will simply add UVar x; inside the class definition:
#include <uobject.h>
using namespace urbi;
class colormap : public UObject
{
public:
colormap(string);
UVar x; // definition of the exported variable
...
};
and then add the binding code in the init method:
int colormap::init(int r, int g, int b)
{
UBindVar(colormap, x);
...
}
Actually, you can put your binding code (UBindVar) anywhere you want, in particular it can be in the C++ object constructor or in the object init method. If you put it in the C++ constructor, it will make the variable available to the base instance (the one that is there at start and that you don't have to 'new'), or if you put it in the 'init' method, only 'newed' objects will have it. This is useful if the base instance is useless because you need to derive it to specify it. In that case you put all your bindings in the 'init' method only and the base instance is just a sort of ghost instance. Note that UObject::derived is a boolean that tells you if your class has been derived with a 'new' or if it is the base class.
You can check, now the colormap.x and ball.x will be there.
To assign a value to x from within your C++ class, simply use it as a normal variable, UObject will do the rest for you:
x = 42; or x= "hello";
The = operator in C++ has been redefined for UVar, so that you don't have to worry and you can assign values to x as you would do it from within URBI.
Now, how to read the variable? We've tried to keep things simple again: you can simply use a C-style casting to get a value in the appropriate C++ type. For the moment, there is not exception raised if an error occurs, so be careful to what you are doing:
x = 42;
printf("Value of x: %d n",(int)x);
x is called a "hook" to the URBI colormap.x variable. Actually, you can define hooks on any variable you like by defining your own UVar instance wherever you like (it will be automatically binded, no need to use UBindVar, the UVar constructor does it). Here are a few examples:
UVar("camera.val");
UVar("camera","val");
UVar* myvar = new UVar("headPan","val");
The reason why you have to call UBindVar for a UVar defined in the body of your class is that this UVar is a non-dynamically allocated UVar called with the default UVar() constructor. Such a UVar doesn't know its name at this stage and the UBindVar macro simply tells it who it is. You don't need this stage with a direct call to the UVar(string) constructor who takes the name as its parameter.
Of course, your C++ object can contain many attributes that will not be exported to URBI and will remain "private" to the C++ class. To make an attribute available to URBI, you need to define it as a UVar or to "UBindVar" one that is part of your object definition.
One important thing that one wants to do with attributes is to monitor them for changes or accesses. This is done by assigning a callback function to the variable, specifying whether you want to be called back on changes or on accesses:
UNotifyChange(x,&colormap::mycallback);
UNotifyAccess(UVar("doo.daa",&colormap::myothercallback);
UNotifyChange("another.variable",&colomap::anothercallback);
Notify on change means that the callback will be called each time the variable is modified on the URBI side (for variables attached to sensors, it means "each time the sensor value is updated"). Notify on access means that the callback will be called each time someone evaluates the variable on the URBI side, so that you have a chance to update its value before the evaluation. In that case, you are advised to put a time-based caching mechanism in your callback if the variable is called frequently inside expressions.
You will typically put those "Notify" lines in the init function or in the constructor of your object, the choice of one over the other being dictated by the same rationale than with UBindVar. Notice that you must pass a pointeur to a function, which must be a method of your object. You have only two types of prototypes available for these callbacks:
UReturn mycallback(); UReturn mycallback(UVar&);
The first one is the simplest and obvious one: the function is called when the condition is met. The second one does the same thing but passes the UVar as a reference parameter so that you can use the same callback with several variables and get the one that is related to the current call.
Just like you did with attributes, you can easily bind a function to the mirrored URBI object. There is not much to do there, simply use the following construct:
int colormap::init(int r, int g, int b)
{
UBindFunction(colormap, dostuff);
...
}
std::string colormap::dostuff(int, float)
{
...
}
This will make the method dostuff visible to the outside. You don't need to worry about parameters, they will be recognized an exported for you. For the moment, you cannot overload a function with this mecanism (and in particular, you cannot overload the init constructor).
Similarily, you can bind an event to a method of your object, so that this method will be called each time the corresponding event is emitted on the URBI side, and you will get the parameters on the way. Simply do:
UBindEvent(colormap, reacttothis);
You can also ask to be notified when the event terminates (as you know, events can last during a certain amount of time in URBI). For example, if you want to be notified by calling the endthis method of your object, simply use:
UBindEvent(colormap, reacttothis); UBindEventEnd(colormap, reacttothis, endthis);
endthis must have a simple prototype like this one:
void colormap::endthis();
You can easily set timers to be called back at regular time intervals. The syntax is:
USetTimer(time_in_ms, &myobject::mycallback);
With mycallback being a method of your object with the following prototype:
UReturn myobject::mycallback();
You cannot use a callback function coming from outside of your object.
For integers, floats and strings the assignement and reading-by-casting of UVar is straitforward. For binary data, like images and sounds, you will need two appropriate types: UImage and USound. Here is a copy of their definition from 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
}
You recognize them, they are the types used in liburbi, no surprise. If your UVar is an image, like "camera.raw", you can simply cast it to a UImage and you will retrieve the relevant information in the appropriate attributes, in particular the binary content will be in data and the size in size. Same thing for a sound.
Be careful if you use camera.val: it might be a jpeg-compressed binary and you should convert it with one of those functions, as described in section #x1-690007.9:
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);
If you want to assign a sound to, let's say speaker.val, simply fill up a USound variable and assign it to the appropriate UVar, the = operator has been redefined to handle this. So far, we handle only wav format.
We have already mentionned the load attribute that is defined as a UVar and bound by default in UObject. This attribute can be used to test in your C++ code whether the object is activated or not on the URBI side. In URBI, a call to "myobject on;" will put load to 1 and a call to "myobject off;" will put it to 0. So, you can easily test in your various functions if you have to do the computation or not, based on the value of load.
This is extremely useful if you want to be able to activate/disactivate some CPU hungry calculation that would otherwise run for nothing in the background. For example, you can turn the ball detection off in Aibo with:
ball off;
Note that this is a broadcastable construct: if you on/off a group, it will recursively propagates to every member of the group. That's exactly what happens behind the scene with a command like motors on;
NB: You also have "myobject switch;" to alternate between on and off.
In UObject definition, the remote attribute is available to know whether your object is running as a remote component or a plugged one. This can be useful when you want to behave differently in both cases, typically handling the transfer of large amount of data or images with or without compression. The colormap example below make use of the remote attribute.
Here is a real example of a colormap object as it is used in the Aibo, to calculate the average position of a blob of color defined by a subspace of the YCrCb color space. You see how we bind a callback to source, which is usually camera. The actual callback is set to the .val or .raw attribute of the source object, depending on the status of the object, remote or not. In remote mode, we want to use jpeg compression and work with the resulting image value, whereas in plugged mode, we can use shared memory on the raw buffer to get a better image without artifacts, and avoid compressing/decompressing for nothing.
You also see how we simply assign values to the x and y attributes and other attributes describing the shape of the blob:
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&);
};
Here, we use ufloat instead of float because ufloat can be adapted to 32bits or 64bits or even no-FPU motherboards and thus it is more suitable for embedded applications.
Now, the main code:
#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);
}
The colormap object is then plugged in the URBI Engine and it is used to create a ball detector in the URBI.INI file:
ball = new colormap("camera",0,255,120,190,150,230,0.0015);
We provide useful scripts to help you build a plugin component or a remote component as easily as possible. Under Linux, this is done with a makefile that you put in the directory of your UObject source code. Under Windows, libraries and sample projects will be provided for common compilers, but for now mingw and the cygwin environnment must be used. The following instruction correspond to a unix-like environment.
To build or use components for a given URBI server, you need to install the SDK corresponding to this server. Download it from the URBI website (or from the robot manufacturer's website), decompress the archive, and to install it, type as root:
make install
Plugins and remote components are built the same way. Copy the file Makefile.module located in /usr/local/share/urbicore into the directory of your component, and rename it Makefile. Its default behavior is to compile all source files in the directory (edit it if you need more complicated stuffs to be done).
To build your component and link it with a specific server, type:
make TARGET=<target> link
replacing <target> with the target name corresponding to your server (for instance: aibo). This command will produce a new URBI server, with your component embeded in it. Replace the existing URBI server with this new one (typically URBI.BIN with aibo) and you are done.
To build your component and make it an autonomous remote module, type:
make TARGET=remote link
This will produce an executable that takes the robot hostname or IP address as its only argument.
Please be aware that some targets have extra-dependencies, such as aibo which requires the OPENR SDK. In other words, you need to install the OPENR SDK before building components in plugin mode for Aibo.
To plug several components into a server, build them as explained above if you have their sources, omitting the link at the end of the make command:
make TARGET=<target>
This will create a set of libraries ready to link. Then in one of the source directories, type
make TARGET=<target> link LIBS=<libs> LIB_DIR=<libdir>
where libs is the names of all the libraries you want to link, and libdir the path to these libraries. As an exemple, if you have an Aibo and you are making a monopoly component in the current directory, and want to plug it with the detectmoney component you downloaded in /home/me/incoming as libdetectmoney.a, type:
make TARGET=aibo link LIBS=detectmoney LIB_DIR=/home/me/incoming
You can either distribute the sources of your component, or the library file (.a) generated by the build process as described above (make TARGET=<target>). People will then be able to link it to an URBI server as explained above. Please note that the library is architecture dependant: a component compiled for the Aibo can't be relinked as a remote module, the sources have to be recompiled. We strongly advice to publish both the source code for rebuilding purposes if your license allows it, AND one or several binary versions for people who just want to link it and use it as a remote component.
The website http://www.urbiforge.com is a platform to exhange components and URBI scripts. You can upload your work there so that the community can benefit from it.