Hello world 4 - RTT Tutorial: Operations

The source code of this tutorial can be found in the GitHub repository.

In this tutorial we will introduce you to operations and how they expose functions so they can be used in scripting and by other processes. To illustrate this two components will be created in HelloWorld.cpp. It is advised to read The OperationCaller/Operation Interface before starting this tutorial.

Contents of HelloWorld.cpp:

#include <rtt/Logger.hpp>
#include <rtt/TaskContext.hpp>

/**
* Include this header in order to call component operations.
*/
#include <rtt/OperationCaller.hpp>
#include <rtt/Component.hpp>

using namespace std;
using namespace RTT;

namespace Example
{

    /**
    * Every component inherits from the 'TaskContext' class.  This base
    * class allow a user to add a primitive to the interface and contain
    * an ExecutionEngine which executes application code.
    */
    class Hello
        : public TaskContext
    {
    protected:

        /**
        * Returns a string.
        */
        string getMessage() {
            return "Hello World";
        }

    public:
        /**
        * This example sets the interface up in the Constructor
        * of the component.
        */
        Hello(std::string name)
            : TaskContext(name)
        {
            this->addOperation("getMessage", &Hello::getMessage, this).doc("Returns a friendly word.");
        }

    };

    /**
    * World is the component that shows how to call an Operation
    * of the Hello component in C++.
    */
    class World
      : public TaskContext
    {
    protected:
      /**
      * This OperationCaller serves to store the
      * call to the Hello component's Operation.
      * It is best practice to have this object as
      * a member variable of your class.
      */
      OperationCaller< string(void) > getMessage;


    public:
      World(std::string name)
    : TaskContext(name, PreOperational)
      {
      }

      bool configureHook()
      {
          // Lookup the Hello component.
          TaskContext* peer = this->getPeer("hello");
          if ( !peer ) {
            log(Error) << "Could not find Hello component!"<<endlog();
            return false;
          }

          // It is best practice to lookup methods of peers in
          // your configureHook.
          getMessage = peer->getOperation("getmessage");
          if ( !getMessage.ready() ) {
            log(Error) << "Could not find Hello.getMessage Operation!"<<endlog();
            return false;
          }
          return true;
      }

      void updateHook() {
        log(Info) << "Receiving from 'hello': " << getMessage() <<endlog();
      }
    };
}

ORO_CREATE_COMPONENT_LIBRARY()
ORO_LIST_COMPONENT_TYPE( Example::Hello )
ORO_LIST_COMPONENT_TYPE( Example::World )

Tutorial 4

Note

This tutorial assumes that you have installed Orocos through the pre-compiled packages distributed via ROS in Ubuntu. If you don’t have it installed, try following the instructions from Installation options.

Now you should have a working Orocos + ROS integration bundle. If you used a different system or installation method, please adapt the following lines to your convenience.

Note

ROS is not needed to run Orocos or to follow this tutorial, but it is a convenient way to quickly get started.

# You can change the next two settings in accordance to your setup
export RTT_TUTORIALS_WS=${HOME}/orocos_tutorials_ws
export ROS_DISTRO=kinetic

# Get the repository with the exercises on place
mkdir -p ${RTT_TUTORIALS_WS}/src
cd ${RTT_TUTORIALS_WS}/src
git clone https://github.com/orocos-toolchain/rtt_examples.git
cd ..

# Build the examples using ROS catkin tools
source /opt/ros/${ROS_DISTRO}/setup.bash
catkin build

Creating an Operation

In our example the Hello component provides an operation, the operation is just a function which returns a string "Hello World". Adding the operation can be done with the addOperation method:

this->addOperation("getMessage", &Hello::getMessage, this).doc("Returns a friendly word.");

Calling an Operation

From the deployer

Start the Orocos deployer (deployer-gnulinux -lInfo), and create the Hello component:

import("hello_4_operations")

loadComponent("hello", "Example::Hello")

// Print the interface of the hello component, the new "getMessage" operation should now be listed.
ls hello

Calling the operation is then as simple as:

hello.getMessage()

Using the OperationCaller

Components can call operations of other components using an instance of OperationCaller. It is considered best practice to have this OperationCaller as a member of your class. The OperationCaller needs to be templated with the signature of the operation you wish to call. In the above example we wish to call string getMessage(), so our OperationCaller looks like this: OperationCaller<string(void)>. In order to be able to call operations of other components, the components must first be connected using connectPeers in the Orocos deployer:

import("hello_4_operations")

loadComponent("hello","Example::Hello")
loadComponent("world","Example::World")

connectPeers("hello","world")

The component that wants to call the operation of the other component first needs to look up the peer using this->getPeer("hello"), and retrieve the operation it wishes to call using peer->getOperation("getmessage"). All this is preferable done in the configureHook method:

bool configureHook()
{
    // Lookup the Hello component.
    TaskContext* peer = this->getPeer("hello");
    if ( !peer ) {
      log(Error) << "Could not find Hello component!"<<endlog();
      return false;
    }

    // It is best practice to lookup methods of peers in
    // your configureHook.
    getMessage = peer->getOperation("getmessage");
    if ( !getMessage.ready() ) {
      log(Error) << "Could not find Hello.getMessage Operation!"<<endlog();
      return false;
    }
}

The operation can then be called using the OperationCaller getMessage, for example in the updateHook:

void updateHook()
{
  log(Info) << "Receiving from 'hello': " << getMessage() <<endlog();
}