Next

Prev

Prev-tail

Tail

Up

Chapter 3
The UObject API

The UObject API can be used to add new objects written in C ++ to the urbiscript language, and to interact from C ++ with the objects that are already defined. We cover the use cases of controlling a physical device (servomotor, speaker, camera…), and interfacing higher-lever components (voice recognition, object detection…) with Urbi.

The C ++ API defines the UObject class. To each instance of a C ++ class deriving from UObject will correspond an urbiscript object sharing some of its methods and attributes. The API provides methods to declare which elements of your object are to be shared. To share a variable with Urbi, you have to give it the type UVar. This type is a container that provides conversion and assignment operators for all types known to Urbi: double, std::string and char*, and the binary-holding structures UBinary, USound and UImage. This type can also read from and write to the liburbi UValue class. The API provides methods to set up callbacks functions that will be notified when a variable is modified or read from Urbi code. Instance methods of any prototype can be rendered accessible from urbiscript, providing all the parameters types and the return type can be converted to/from UValue.

 3.1 Compiling UObjects
  3.1.1 Compiling by hand
  3.1.2 The umake-* family of tools
  3.1.3 Using the Visual C ++ Wizard
 3.2 Creating a class, binding variables and functions
 3.3 Creating new instances
 3.4 Binding functions
  3.4.1 Simple binding
  3.4.2 Multiple bindings
  3.4.3 Asynchronous binding
 3.5 Notification of a variable change or access
 3.6 Data-flow based programming: exchanging UVars
 3.7 Timers
 3.8 The special case of sensor/effector variables
 3.9 Using Urbi variables
 3.10 Emitting events
 3.11 UObject and Threads
 3.12 Using binary types
  3.12.1 UVar conversion and memory management
  3.12.2 0-copy mode
 3.13 Using hubs to group objects
 3.14 Sending urbiscript code
 3.15 Using RTP transport in remote mode
  3.15.1 Enabling RTP
  3.15.2 Per-UVar control of RTP mode
 3.16 Extending the cast system
  3.16.1 Principle
  3.16.2 Casting simple structures

3.1 Compiling UObjects

UObjects can be compiled easily directly with any regular compiler. Nevertheless, Urbi SDK provides two tools to compile UObject seamlessly.

In the following sections, we will try to compile a shared library named ‘factory.so’ (or ‘factory.dll’ on Windows platforms) from a set of four files (‘factory.hh’, ‘factory.cc’, ‘ufactory.hh’, ‘ufactory.cc’). These files are stored in a ‘factory.uob’ directory; its name bares no importance, yet the ‘*.uob’ extension makes clear that it is a UObject.

In what follows, urbi-root denotes the top-level directory of your Urbi SDK package, see Section 15.2.

3.1.1 Compiling by hand

On Unix platforms, compiling by hand into a shared library is straightforward:

 
$ g++ -I urbi-root/include \ 
      -fPIC -shared \ 
      factory.uob/*cc -o factory.so 
$ file factory.so 
factory.so: ELF 32-bit LSB shared object, Intel 80386, \ 
  version 1 (SYSV), dynamically linked, not stripped  

On Mac OS X the flags ‘-Wl,-undefined,dynamic_lookup’ are needed:

 
$ g++ -I urbi-root/include \ 
      -shared -Wl,-undefined,dynamic_lookup \ 
      factory.uob/*.cc -o factory.so 
$ file factory.so 
factory.so: Mach-O 64-bit dynamically linked shared library x86_64  

3.1.2 The umake-* family of tools

umake can be used to compile UObjects. See Section 19.7 for its documentation.

You can give it a list of files to compile:

 
$ umake -q --shared-library factory.uob/*.cc -o factory.so 
umake: running to build library.  

or directories in which C ++ sources are looked for:

 
$ umake -q --shared-library factory.uob -o factory.so 
umake: running to build library.  

or finally, if you give no argument at all, the sources in the current directory:

 
$ cd factory.uob 
$ umake -q --shared-library -o factory.so 
umake: running to build library.  

3.1.3 Using the Visual C ++ Wizard

If you installed Urbi SDK using its installer, and if you had Visual C ++ installed, then the UObject wizard was installed. Use it to create your UObject code:

PIC

Then, compile your UObject.

PIC

And run it.

PIC

3.2 Creating a class, binding variables and functions

Let’s illustrate those concepts by defining a simple object: adder. This object has one variable v, and a method add that returns the sum of this variable and its argument.

To summarize:

3.3 Creating new instances

When you start an Urbi server, an object of each class registered with UStart is created with the same name as the class. New instances can be created from Urbi using the new method. For each instance created in Urbi, a corresponding instance of the C ++ object is created. You can get the arguments passed to the constructor by defining and binding a method named init with the appropriate number of arguments.

3.4 Binding functions

3.4.1 Simple binding

You can register any member function of your UObject using the macro

UBindFunction(class-name, function-name).

Once done, the function can be called from urbiscript.

The following types for arguments and return value are supported:

The procedure to register new types to this system is explained in Section 3.16.

3.4.2 Multiple bindings

If you have multiple functions to bind, you can use the UBindFunctions macro to bind multiple functions at once:

UBindFunctions(class-name, function1, function2...).

3.4.3 Asynchronous binding

Functions bound using UBindFunction are called synchronously, and thus block everything until they return.

If you wish to bind a function that requires a non-negligible amount of time to execute, you can have it execute in a separate thread by calling

UBindThreadedFunction(class-name, function-name, lockMode).

The function code will be executed in a separate thread without breaking the urbiscript execution semantics.

The lockMode argument can be used to prevent parallel execution of multiple bound functions if your code is not thread-safe. It can be any of LOCK_NONE, LOCK_FUNCTION, LOCK_INSTANCE, LOCK_CLASS or LOCK_MODULE. When set to LOCK_NONE, no locking is performed. Otherwise, it limits parallel executions to:

There is however a restriction: you cannot mix multiple locking modes: for instance a function bound with LOCK_FUNCTION mode will not prevent another function bound with LOCK_INSTANCE from executing in parallel.

You can perform your own locking using semaphores if your code needs a more complex locking model.

You can limit the maximum number of threads that can run in parallel by using the setThreadLimit function.

3.5 Notification of a variable change or access

You can register a function that will be called each time a variable is modified or accessed (for embedded components only) by calling UNotifyChange and UNotifyAccess, passing either an UVar or a variable name as first argument, and a member function of your UObject as second argument. This function can take zero or one argument of any type. If the argument is of type UVar&, the value will be a reference to the UVar being accessed or modified. If it is of any other type, the system will try to convert the current value of the UVar to this type and pass this value to the function. The notifyChange callback function is called after the variable value is changed, whereas the notifyAccess callback is called before the variable is accessed, giving you the possibility to update its value.

Notify functions can be unregistered by calling the unnotify function of the UVar class.

3.6 Data-flow based programming: exchanging UVars

The UNotifyChange and UNotifyAccess features can be used to link multiple UObjects together, and perform data-flow based programming: the UNotifyChange can be called to monitor UVars from other UObjects. Those UVars can be transmitted through bound function calls.

One possible pattern is to have each data-processing UObject take its input from monitored UVars, given in its constructor, and output the result of its processing in other UVars. Consider the following example of an object-tracker:

 
class ObjectTracker: public urbi::UObject 
{ 
  ObjectTracker(const std::string& n) 
    : urbi::UObject(n) 
  { 
    // Bind our constructor. 
    UBindFunction(ObjectTracker, init); 
  } 
  // Take our data source in our constructor. 
  void init(UVar& image) 
  { 
    UNotifyChange(image, &ObjectTracker::onImage); 
    // Bind our output variable. 
    UBindVar(ObjectTracker, val); 
  } 
  void onImage(UVar& src) 
  { 
    UBinary b = src; 
    // Processing here. 
    val = processing_result; 
  } 
  UVar val; 
}; 
UStart(ObjectTracker);  

The following urbiscript code would be used to initialize an ObjectTracker given a camera:

 
var tracker = ObjectTracker.new(camera.getSlot("val"));  

An other component could then take the tracker output as its input.

Using this model, chains of processing elements can be created. Each time the UObject at the start of the chain updates, all the notifyChange will be called synchronously in cascade to update the state of the intermediate components.

3.7 Timers

The API provides two methods to have a function called periodically:

3.8 The special case of sensor/effector variables

In Urbi, a variable can have a different meaning depending on whether you are reading or writing it: you can use the same variable to represent the target value of an effector and the current value measured by an associated sensor. This special mode is activated by the UObject defining the variable by calling UOwned after calling UBindVar. This call has the following effects:

3.9 Using Urbi variables

The C++ class UVar is used to represent any Urbi slot in C++. To bind the UVar to a specific slot, pass its name to the UVar constructor, or its init method. Once the UVar is bound, you can write any compatible type to it, and the new value will be visible in urbiscript. Similarly, you can cast the UVar (or use the as() method) to convert the current urbiscript value held to any compatible type.

Compatible types are the same as for bound functions (see Section 3.16).

 
// Set the camera format to 0 if it is 1. 
UVar v; 
v.init("camera", "format"); 
if (v == 1) 
 v = 0;  

Some care must be taken in remote mode: changes on the variable coming from Urbi code or an other module can take time to propagate to the UVar. By default, all changes to the value will be sent to the remote UObject. To have more control on the bandwidth used, you can disable the automatic update by calling unnotify(). Then you can get the value on demand by calling UVar::syncValue().

 
UVar v("Global", "x"); 
send("every|(100ms) Global.x = time,"); 
// At this point, v is updated approximately every 100 milliseconds. 
 
v.unnotify(); 
// At this point v is no longer updated. If v was the only UVar pointing to 
// Global.x’, the value is no longer transmitted. 
 
v.syncValue(); 
// The previous call will ask for the value of Global.x once, and block until 
// the value is written to v.  

You can read and write all the Urbi properties of an UVar by reading and writing the appropriate UProp object in the UVar.

3.10 Emitting events

The UEvent class can be used to create and emit urbiscript events. Instances are created and initialized exactly as UVar: either by using the UBindEvent macro, or by calling one of its constructors or the init function.

Once initialized, the emit function will trigger the emission of the associated urbiscript event. It can be called with any number of arguments, of any compatible type.

3.11 UObject and Threads

The UObject API is thread-safe in both plugin and remote mode: All API calls including operations on UVar can be performed from any thread.

3.12 Using binary types

Urbi can store binary objects of any type in a generic container, and provides specific structures for sound and images. The generic containers is called UBinary and is defined in the ‘urbi/ubinary.hh’ header. It contains an enum field type giving the type of the binary (UNKNOWN, SOUND or IMAGE), and an union of a USound and UImage struct containing a pointer to the data, the size of the data and type-specific meta-information.

3.12.1 UVar conversion and memory management

The UBinary manages its memory: when destroyed (or going out-of-scope), it frees all its allocated data. The USound and UImage do not.

Reading an UBinary from a UVar, and writing a UBinary, USound or UImage to an UVar performs a deep-copy of the data (by default, see below).

Reading a USound or UImage from an UVar performs a shallow copy. Modifying the data is not allowed in that case.

3.12.2 0-copy mode

In plugin mode, you can setup any UVar in 0-copy mode by calling setBypass(true). In this mode, binary data written to the UVar is not copied, but a reference is kept. As a consequence, the data is only available from within registered notifyChange callbacks. Those callbacks can use UVar::val() or cast the UVar to a UBinary & to retrieve the reference. Attempts to read the UVar from outside notifyChange will result in nil being returned.

3.13 Using hubs to group objects

Sometimes, you need to perform actions for a group of UObjects, for instance devices that need to be updated together. The API provides the UObjectHub class for this purpose. To create a hub, simply declare a subclass of UObjectHub, and register it by calling once the macro UStartHub (class-name). A single instance of this class will then be created upon server start-up. UObject instances can then register to this hub by calling URegister (hub-class-name). Timers can be attached to UObjectHub the same way as to UObject (see Section 3.7). The kernel will call the update() method of all UObject before calling the update() method of the hub. A hub instance can be retrieved by calling getUObjectHub (string classname). The hub also holds the list of registered UObject in its members attribute.

3.14 Sending urbiscript code

If you need to send urbiscript code to the server, the URBI() macro is available, as well as the send() function. You can either pass it a string, or directly Urbi code inside a double pair of parentheses:

 
send ("myTag:1+1;"); 
 
URBI (( at (myEvent?(var x)) { myTag:echo x; }; ));  

You can also use the call method to make an urbiscript function call:

 
// C++ equivalent of urbiscript System.someFunc(12, "foo");’ 
call("System", "someFunc", 12, "foo");  

3.15 Using RTP transport in remote mode

By default, Urbi uses TCP connections for all communications between the engine and remote UObjects. Urbi also supports the UDP-based RTP protocol for more efficient transmission of updated variable values. RTP will provide a lower latency at the cost of possible packet loss, especially in bad wireless network conditions.

3.15.1 Enabling RTP

To enable RTP connections, both the engine and the remote-mode urbi-launch containing your remote UObject must load the RTP UObject. This can be achieved by passing urbi/rtp as an extra argument to both urbi-launch command lines (one for the engine, the other for your remote UObject).

Once done, all binary data transfer (like sound and image) in both directions will by default use a RTP connection.

3.15.2 Per-UVar control of RTP mode

You can control whether a specific UVar uses RTP mode by calling its useRTP(bool) function. Each binary-type UVar will have its own RTP connection, and all non-binary UVar will share one.

From urbiscript, you can also write to the rtp slot of each UVar. Existing notifies will be modified to use rtp if you set it to true.

3.16 Extending the cast system

3.16.1 Principle

The same cast system is used both for bound function’s arguments and return values, and for reading/writing UVar.

Should you want to add new type MyType to the system you must define two functions:

 
namespace urbi 
{ 
  void operator, (UValue& v, const MyType& t) 
  { // Here you must fill v with the serialized representation of t. 
  } 
  template<> struct uvalue_caster<MyType> 
  { 
    MyType operator()(UValue& v) 
    { // Here you must return a MyType made with the information in v. 
    } 
  } 
}  

Once done, you will be able without any other change to

3.16.2 Casting simple structures

The system provides facilities to serialize simple structures by value between C++ and urbiscript. This system uses two declarations of each structure, one in C++ and the other in Urbi, and maps between the two.

Here is a complete commented example to map a simple Point structure between urbiscript and C++.

 
struct Point 
{ 
  // Your struct must have a default constructor. 
  Point():x(0), y(0) {} 
  double x,y; 
}; 
 
// Declare the structure to the cast system. First argument is the struct, 
// following arguments are the field names. 
URBI_REGISTER_STRUCT(Point, x, y);  

 
// Declare the urbiscript structure. It must be globally accessible, and 
// inheriting from UValueSerializable. 
class Global.Point: UValueSerializable 
{ 
  var x; 
  var y; 
  function init(xx=0, yy=0) 
  { 
     x = xx| 
     y = yy 
  }; 
  function asString() 
  { 
    "<" + x +", " + y  + ">" 
  }; 
}; 
// Add the class to Serializables to register it. 
var Serializables.Point = Point;  

Once done, you can call bound functions taking a C++ Point by passing them an urbiscript Point and exchange Point between both worlds through UVar read/write.