Provides vs Requires interfaces

Users can express the 'provides' interface of an Orocos Component. However, there is no easy way to express which other components a component requires. The notable exception is data flow ports, which have in-ports (requires) and out-ports (provides). It is however not possible to express this requires interface for the execution flow interface, thus for methods, commands/messages and events. This omission makes the component specification incomplete.

One of the first questions raised is if this must be expressed in C++ or during 'modelling'. That is, UML can express the requires dependency, so why should the C++ code also contain it in the form of code ? It should only contain it if you can't generate code from your UML model. Since this is not yet available for Orocos components, there is no other choice than expressing it in C++.

A requires interface specification should be optional and only be present for:

  • completing the component specification, allowing better review and understanding
  • automatically connecting component 'execution' interfaces, such that the manual lookup work which you need to write today can be omitted.

We apply this in code examples to various proposed primitives in the pages below.

New Command API

Commands are no longer a part of the TaskContext API. They are helper classes which replicate the old RTT 1.0 behaviour. In order to setup commands more easily, it is allowed to register them as a 'requires()' interface.

This is all very experimental.

/**
 * Provider of a Message with command-like semantics
 */
class TaskA    : public TaskContext
{
    Message<void(double)>   message;
    Method<bool(double)>    message_is_done;
    Event<void(double)>     done_event;
 
    void mesg(double arg1) {
        return;
    }
 
    bool is_done(double arg1) {
        return true;
    }
 
public:
 
    TaskA(std::string name)
        : TaskContext(name),
          message("Message",&TaskA::mesg, this),
          message_is_done("MessageIsDone",&TaskA::is_done, this),
          done_event("DoneEvent")
    {
        this->provides()->addMessage(&message, "The Message", "arg1", "Argument 1");
        this->provides()->addMethod(&method, "Is the Message done?", "arg1", "Argument 1");
        this->provides()->addEvent(&done_event, "Emited when the Message is done.", "arg1", "Argument 1");
    }
 
};
 
class TaskB   : public TaskContext
{
    // RTT 1.0 style command object
    Command<bool(double)>   command1;
    Command<bool(double)>   command2;
 
public:
 
    TaskB(std::string name)
        : TaskContext(name),
          command1("command1"),
          command2("command2")
    {
        // the commands are now created client side, you
        // can not add them to your 'provides' interface
        command1.useMessage("Message");
        command1.useCondition("MessageIsDone");
        command2.useMessage("Message");
        command2.useEvent("DoneEvent");
 
        // this allows automatic setup of the command.
        this->requires()->addCommand( &command1 );
        this->requires()->addCommand( &command2 );
    }
 
    bool configureHook() {
        // setup is done during deployment.
        return command1.ready() && command2.ready();
    }
 
    void updateHook() {
        // calls TaskA:
        if ( command1.ready() && command2.ready() )
            command1( 4.0 );
        if ( command1.done() && command2.ready() )
            command2( 1.0 );
    }
};
 
int ORO_main( int, char** )
{
    // Create your tasks
    TaskA ta("Provider");
    TaskB tb("Subscriber");
 
    connectPeers(ta, tb);
    // connects interfaces.
    connectInterfaces(ta, tb);
    return 0;
}

New Event API

The idea of the new Event API is that: 1. only the owner of the event can emit the event (unless the event is also added as a Method or Message) 2. Only methods or message objects can subscribe to events.

/**
 * Provider of Event
 */
class TaskA    : public TaskContext
{
    Event<void(string)>   event;
 
public:
 
    TaskA(std::string name)
        : TaskContext(name),
          event("Event")
    {
        this->provides()->addEvent(&event, "The Event", "arg1", "Argument 1");
        // OR:
        this->provides("FooInterface")->addEvent(&event, "The Event", "arg1", "Argument 1");
 
        // If you want the user to let him emit the event:
        this->provides()->addMethod(&event, "Emit The Event", "arg1", "Argument 1");
    }
 
    void updateHook() {
        event("hello world");
    }
};
 
/**
 * Subscribes a local Method and a Message to Event
 */
class TaskB   : public TaskContext
{
    Message<void(string)>   message;
    Method<void(string)>    method;
 
    // Message callback
    void mesg(double arg1) {
        return;
    }
 
    // Method callback
    int meth(double arg1) {
        return 0;
    }
 
public:
 
    TaskB(std::string name)
        : TaskContext(name),
          message("Message",&TaskB::mesg, this),
          method("Method",&TaskB::meth, this)
    {
        // optional:
        // this->provides()->addMessage(&message, "The Message", "arg1", "Argument 1");
        // this->provides()->addMethod(&method, "The Method", "arg1", "Argument 1");
 
        // subscribe to event:
        this->requires()->addCallback("Event", &message);
        this->requires()->addCallback("Event", &method);
 
        // OR:
        // this->provides("FooInterface")->addMessage(&message, "The Message", "arg1", "Argument 1");
        // this->provides("FooInterface")->addMethod(&method, "The Method", "arg1", "Argument 1");
 
        // subscribe to event:
        this->requires("FooInterface")->addCallback("Event", &message);
        this->requires("FooInterface")->addCallback("Event", &method);
    }
 
    bool configureHook() {
        // setup is done during deployment.
        return message.ready() && method.ready();
    }
 
    void updateHook() {
        // we only receive
    }
};
 
int ORO_main( int, char** )
{
    // Create your tasks
    TaskA ta("Provider");
    TaskB tb("Subscriber");
 
    connectPeers(ta, tb);
    // connects interfaces.
    connectInterfaces(ta, tb);
    return 0;
}

New Message API

This use case shows how one can use messages in the new API. The unchanged method is added for comparison. Note that I have also added the provides() and requires() mechanism such that the RTT 1.0 construction:

  method = this->getPeer("PeerX")->getMethod<int(double)>("Method");

is no longer required. The connection is made similar as data flow ports are connected.

/**
 * Provider
 */
class TaskA    : public TaskContext
{
    Message<void(double)>   message;
    Method<int(double)>     method;
 
    void mesg(double arg1) {
        return;
    }
 
    int meth(double arg1) {
        return 0;
    }
 
public:
 
    TaskA(std::string name)
        : TaskContext(name),
          message("Message",&TaskA::mesg, this),
          method("Method",&TaskA::meth, this)
    {
        this->provides()->addMessage(&message, "The Message", "arg1", "Argument 1");
        this->provides()->addMethod(&method, "The Method", "arg1", "Argument 1");
        // OR:
        this->provides("FooInterface")->addMessage(&message, "The Message", "arg1", "Argument 1");
        this->provides("FooInterface")->addMethod(&method, "The Method", "arg1", "Argument 1");
    }
 
};
 
class TaskB   : public TaskContext
{
    Message<void(double)>   message;
    Method<int(double)>     method;
 
public:
 
    TaskB(std::string name)
        : TaskContext(name),
          message("Message"),
          method("Method")
    {
        this->requires()->addMessage( &message );
        this->requires()->addMethod( &method );
        // OR:
        this->requires("FooInterface")->addMessage( &message );
        this->requires("FooInterface")->addMethod( &method );
    }
 
    bool configureHook() {
        // setup is done during deployment.
        return message.ready() && method.ready();
    }
 
    void updateHook() {
        // calls TaskA:
        method( 4.0 );
        // sends two messages:
        message( 1.0 );
        message( 2.0 );
    }
};
 
int ORO_main( int, char** )
{
    // Create your tasks
    TaskA ta("Provider");
    TaskB tb("Subscriber");
 
    connectPeers(ta, tb);
    // connects interfaces.
    connectInterfaces(ta, tb);
    return 0;
}

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);
         */
    }
};