New Method, Operation, Service API

This page shows some use cases on how to use the newly proposed services classes in RTT 2.0.

WARNING: This page assumes the reader has familiarity with the current RTT 1.x API.

First, we introduce the new classes that would be added to the RTT:

#include <rtt/TaskContext.hpp>
#include <string>
 
using RTT::TaskContext;
using std::string;
 
/**************************************
 * PART I: New Orocos Classes
 */
 
/**
 * An operation is a function a component offers to do.
 */
template<class T>
class Operation {};
 
/**
 * A Service collects a number of operations.
 */
class ServiceProvider {
public:
    ServiceProvider(string name, TaskContext* owner);
};
 
/**
 * Is the invocation of an Operation.
 * Methods can be executed blocking or non blocking,
 * in the latter case the caller can retrieve the results
 * later on.
 */
template<class T>
class Method {};
 
/**
 * A ServiceRequester collects a number of methods
 */
class ServiceRequester {
public:
    ServiceRequester(string name, TaskContext* owner);
 
    bool ready();
};

What is important to notice here is the symmetry:

 (Operation, ServiceProvider) <-> (Method, ServiceRequester).
The left hand side is offering services, the right hand side is using the services.

First we define that we provide a service. The user starts from his own C++ class with virtual functions. This class is then implemented in a component. A helper class ties the interface to the RTT framework:

/**************************************
 * PART II: User code for PROVIDING a service
 */
 
/**
 * Example Service as abstract C++ interface (non-Orocos).
 */
class MyServiceInterface {
public:
    /**
     * Description.
     * @param name Name of thing to do.
     * @param value Value to use.
     */
    virtual int foo_function(std::string name, double value) = 0;
 
    /**
     * Description.
     * @param name Name of thing to do.
     * @param value Value to use.
     */
    virtual int bar_service(std::string name, double value) = 0;
};
 
/**
 * MyServiceInterface exported as Orocos interface.
 * This could be auto-generated from reading MyServiceInterface.
 *
 */
class MyService {
protected:
    /**
     * These definitions are not required in case of 'addOperation' below.
     */
    Operation<int(const std::string&,double)> operation1;
    Operation<int(const std::string&,double)> operation2;
 
    /**
     * Stores the operations we offer.
     */
    ServiceProvider provider;
public:
    MyService(TaskContext* owner, MyServiceInterface* service)
    : provider("MyService", owner),
      operation1("foo_function"), operation2("bar_service")
    {
                // operation1 ties to foo_function and is executed in caller's thread.
        operation1.calls(&MyServiceInterface::foo_function, service, Service::CallerThread);
        operation1.doc("Description", "name", "Name of thing to do.", "value", "Value to use.");
                provider.addOperation( operation1 );
 
        // OR: (does not need operation1 definition above)
        // Operation executed by caller's thread:
        provider.addOperation("foo_function", &MyServiceInterface::foo_function, service, Service::CallerThread)
                .doc("Description", "name", "Name of thing to do.", "value", "Value to use.");
 
        // Operation executed in component's thread:
        provider.addOperation("bar_service", &MyServiceInterface::bar_service, service, Service::OwnThread)
                .doc("Description", "name", "Name of thing to do.", "value", "Value to use.");
    }
};

Finally, any component is free to provide the service defined above. Note that it shouldn't be that hard to autogenerate most of the above code.

/**
 * A component that implements and provides a service.
 */
class MyComponent : public TaskContext, protected MyServiceInterface
{
    /**
     * The class defined above.
     */
    MyService serv;
public:
    /**
     * Just pass on TaskContext and MyServiceInterface pointers:
     */
    MyComponent() : TaskContext("MC"), serv(this,this)
    {
 
    }
 
protected:
    // Implements MyServiceInterface
    int foo_function(std::string name, double value)
    {
        //...
        return 0;
    }
    // Implements MyServiceInterface
    int bar_service(std::string name, double value)
    {
        //...
        return 0;
    }
};

The second part is about using this service. It creates a ServiceRequester object that stores all the methods it wants to be able to call.

Note that both ServiceRequester below and ServiceProvider above have the same name "MyService". This is how the deployment can link the interfaces together automatically.

/**************************************
 * PART II: User code for REQUIRING a service
 */
 
/**
 * We need something like this to define which services
 * our component requires.
 * This class is written explicitly, but it can also be done
 * automatically, as the example below shows.
 *
 * If possible, this class should be generated too.
 */
class MyServiceUser {
    ServiceRequester rservice;
public:
    Method<int(const string&, double)> foo_function;
    MyServiceUser( TaskContext*  owner )
    : rservice("MyService", owner), foo_function("foo_function")
      {
        rservice.requires(foo_function);
      }
};
 
/**
 * Uses the MyServiceUser helper class.
 */
class UserComponent2 : public TaskContext
{
    // also possible to (privately) inherit from this class.
    MyServiceUser mserv;
public:
    UserComponent2() : TaskContext("User2"), mserv(this)
    {
    }
 
    bool configureHook() {
        if ( ! mserv->ready() ) {
            // service not ready
            return false;
        }
    }
 
    void updateHook() {
        // blocking:
        mserv.foo_function.call("name", 3.14);
        // etc. see updateHook() below.
    }
};

The helper class can again be omitted, but the Method<> definitions must remain in place (in contrast, the Operation<> definitions for providing a service could be omitted).

The code below also demonstrates the different use cases for the Method object.

/**
 * A component that uses a service.
 * This component doesn't need MyServiceUser, it uses
 * the factory functions instead:
 */
class UserComponent : public TaskContext
{
    // A definition like this must always be present because
    // we need it for calling. We also must provide the function signature.
    Method<int(const string&, double)> foo_function;
public:
    UserComponent() : TaskContext("User"), foo_function("foo_function")
    {
        // creates this requirement automatically:
        this->requires("MyService")->add(&foo_function);
    }
 
    bool configureHook() {
        if ( !this->requires("MyService")->ready() ) {
            // service not ready
            return false;
        }
    }
 
    /**
     * Use the service
     */
    void updateHook() {
        // blocking:
        foo_function.call("name", 3.14);
        // short/equivalent to call:
        foo_function("name", 3.14);
 
        // non blocking:
        foo_function.send("name", 3.14);
 
        // blocking collect of return value of foo_function:
        int ret = foo_function.collect();
 
        // blocking collect of any arguments of foo_function:
        string ret1; double ret2;
        int ret = foo_function.collect(ret1, ret2);
 
        // non blocking collect:
        int returnval;
        if ( foo_function.collectIfDone(ret1,ret2,returnval) ) {
            // foo_function was done. Any argument that needed updating has
            // been updated.
        }
    }
};

Finally, we conclude with an example of requiring the same service multiple times, for example, for controlling two stereo-vision cameras.

/**
 * Multi-service case: use same service multiple times.
 * Example: stereo vision with two cameras.
 */
class UserComponent3 : public TaskContext
{
    // also possible to (privately) inherit from this class.
    MyVisionUser vision;
public:
    UserComponent3() : TaskContext("User2"), vision(this)
    {
        // requires a service exactly two times:
        this->requires(vision)["2"];
        // OR any number of times:
        // this->requires(vision)["*"];
        // OR range:
        // this->requires(vision)["0..2"];
    }
 
    bool configureHook() {
        if ( ! vision->ready() ) {
            // only true if both are ready.
            return false;
        }
 
    }
 
    void updateHook() {
        // blocking:
        vision[0].foo_function.call("name", 3.14);
        vision[1].foo_function.call("name", 3.14);
        // or iterate:
        for(int i=0; i != vision.interfaces(); ++i)
            vision[i].foo_function.call("name",3.14);
        // etc. see updateHook() above.
 
        /* Scripting equivalent:
         * for(int i=0; i != vision.interfaces(); ++i)
         *   do vision[i].foo_function.call("name",3.14);
         */
    }
};