RTT 2.0 has unified events, commands and methods in the Operation interface.
This is how a function is added to the component interface:
#include <rtt/Operation.hpp>; using namespace RTT; class MyTask : public RTT::TaskContext { public: string getType() const { return "SpecialTypeB" } // ... MyTask(std::string name) : RTT::TaskContext(name), { // Add the a C++ method to the operation interface: addOperation( "getType", &MyTask::getType, this ) .doc("Read out the name of the system."); } // ... }; MyTask mytask("ATask");
The writer of the component has written a function 'getType()' which returns a string that other components may need. In order to add this operation to the Component's interface, you use the TaskContext's addOperation function. This is a short-hand notation for:
// Add the C++ method to the operation interface: provides()->addOperation( "getType", &MyTask::getType, this ) .doc("Read out the name of the system.");
Meaning that we add 'getType()' to the component's main interface (also called 'this' interface). addOperation takes a number of parameters: the first one is always the name, the second one a pointer to the function and the third one is the pointer to the object of that function, in our case, MyTask itself. In case the function is a C function, the third parameter may be omitted.
If you don't want to polute the component's this interface, put the operation in a sub-service:
// Add the C++ method objects to the operation interface: provides("type_interface") ->addOperation( "getType", &MyTask::getType, this ) .doc("Read out the name of the system.");
The code above dynamically created a new service object 'type_interface' to which one operation was added: 'getType()'. This is similar to creating an object oriented interface with one function in it.
Your code needs a few things before it can call a component's operation:
Combining these three givens, we must create an OperationCaller object that will manage our call to 'getType':
#include <rtt/OperationCaller.hpp> //... // In some other component: TaskContext* a_task_ptr = getPeer("ATask"); // create a OperationCaller<Signature> object 'getType': OperationCaller<string(void)> getType = a_task_ptr->getOperation("getType"); // lookup 'string getType(void)' // Call 'getType' of ATask: cout << getType() <<endl;
A lot of work for calling a function no ? The advantages you get are these:
var string result = ""; set result = ATask.getType();
// Add the C++ method to the operation interface: // Execute function in component's thread: provides("type_interface") ->addOperation( "getType", &MyTask::getType, this, OwnThread ) .doc("Read out the name of the system.");
So this causes that when getType() is called, it gets queued for execution in the ATask component, is executed by its ExecutionEngine, and when done, the caller will resume. The caller (ie the OperationCaller object) will not notice this change of execution path. It will wait for the getType function to complete and return the results.
// This first part is equal to the example above: #include <rtt/OperationCaller.hpp> //... // In some other component: TaskContext* a_task_ptr = getPeer("ATask"); // create a OperationCaller<Signature> object 'getType': OperationCaller<string(void)> getType = a_task_ptr->getOperation("getType"); // lookup 'string getType(void)' // Here it is different: // Send 'getType' to ATask: SendHandle<string(void)> sh = getType.send(); // Collect the return value 'some time later': sh.collect(); // blocks until getType() completes cout << sh.retn() <<endl; // prints the return value of getType().
Other variations on the use of SendHandle are possible, for example polling for the result or retrieving more than one result if the arguments are passed by reference. See the Component Builder's Manual for more details.