YARP
Yet Another Robot Platform
Thrift IDL in YARP: simple tutorial

This tutorial shows how to use the Apache Thrift Interface Definition Language (and relative compiler) to easily define RPC-based interfaces for YARP Modules.

Introduction

Apache Thrift allows to define service interfaces in a simple definition file. Taking that file as input, a compiler generates source code which handles the RPC communication for commands and replies defined in the interface. This source code can therefore be used by a server that executes the commands and by clients that send requests to the server by means of simple function calls.

Step One: Interface Definition

Let's assume that we want to create a new module that can receive commands on a RPC port to start/stop execution, and get/set some value. We can create a .thrift text file that defines a Thrift service interface for our module like this:

#demo.thrift
service Demo {
i32 get_answer();
bool set_answer(1:i32 rightAnswer)
i32 add_one(1:i32 x);
bool start();
bool stop();
bool is_running();
}

where i32 is defined by the Thrift type system as a 32-bit signed integer, and bool is a boolean value (true or false), one byte. Note that each argument of a function is associated to a unique, positive integer identifier. For a complete explanation of the Thrift IDL, please see Thrift IDL in YARP: advanced tutorial.

Step Two: Source code generation

Now that we have our nice and clear interface definition, we want to generate the source code that provides this interface in YARPic terms, and implements the RPC communication process that occurs between a client that invokes a function call and the server that exectutes it.

Using CMake, generation of code for our demo.thrift file can be performed automatically during the configuration process. We create a CMakeLists.txt configuration file in which we call the yarp_idl_to_dir macro:

#CMakeList.txt
cmake_minimum_required(VERSION 3.12)
#find YARP
find_package(YARP REQUIRED)
#compile definition file to generate source code into the desired directory
set(generated_libs_dir "${CMAKE_CURRENT_SOURCE_DIR}")
yarp_idl_to_dir(INPUT_FILES demo.thrift
OUTPUT_DIR ${generated_libs_dir}
SOURCES_VAR sources
HEADERS_VAR headers
INCLUDE_DIRS_VAR include_dirs)
# generated source files now listed in ${sources}
# generated header files now listed in ${headers}
# paths to include now listed in ${include_dirs}

We can now run CMake to set up an out-of-source tree build from command line:

$ cd <where_we_put_demo.thrift_and_CMakeLists.txt>
$ mkdir build && cd build
$ cmake -D ALLOW_IDL_GENERATION=ON ../

The YarpIDL macro defines a CMake "advanced" option, ALLOW_IDL_GENERATION, which is by default set to OFF if generated code is already present in the desired directory; we enable it with the -D ALLOW_IDL_GENERATION=ON option. We should now see two folders, "include" and "src", appear in the source tree, together with a demo_thrift.cmake file that helps CMake keep track of the generated files. Inside include, we find a Demo.h header file, which provides the interface:

class Demo : public yarp::os::Wire {
public:
Demo() { yarp().setOwner(*this); }
virtual int32_t get_answer();
virtual bool set_answer(const int32_t rightAnswer);
virtual int32_t add_one(const int32_t x);
virtual bool start();
virtual bool stop();
virtual bool is_running();
virtual bool read(yarp::os::ConnectionReader& connection);
};

src instead contains a Demo.cpp file that provides implementation for the RPC communication, but there's no need to bother with it: all we need to care about is the interface!

Step Three: Interface Implementation

It is now time to provide an implementation for the functions we defined in the service interface. Let's create a DemoServer.cpp file in which:

  • we include the interface definition
    #include <yarp/os/all.h>
    #include <Demo.h>
  • we declare a derived class that overrides the methods of the interface:
    class DemoServer : public Demo
    {
    // Class members declaration
    int32_t answer;
    bool isRunning;
    public:
    DemoServer();
    // function declarations, copied from Demo.h
    int32_t get_answer() override;
    bool set_answer(int32_t rightAnswer) override;
    int32_t add_one(const int32_t x) override;
    bool start() override;
    bool stop() override;
    bool is_running() override;
    };
  • we provide an implementation for these methods, for example:
    int32_t DemoServer::get_answer()
    {
    std::cout << "The answer is "<< answer << std::endl;
    return answer;
    }
  • finally, we need to create an istance of the DemoServer class and attach it to a YARP port; a simple test can consist in this main() function:
    int main(int argc, char *argv[])
    {
    DemoServer demoServer;
    demoServer.yarp().attachAsServer(port);
    if (!port.open("/demoServer")) {
    return 1;
    }
    while (true) {
    printf("Server running happily\n");
    }
    port.close();
    return 0;
    }

Client use

The thirft engine generates all the required code for the client. Clients can directly invoke remote procedures on the server using a proxy object (generated by thrift) attached to a YARP port.

Simple example:

#include <iostream>
#include <yarp/os/all.h>
#include <Demo.h>
using namespace yarp::os;
int main(int argc, char *argv[])
{
Property config;
config.fromCommand(argc,argv);
// This port will be used to talk to the remote server
Port client_port;
std::string servername= config.find("server").asString().c_str();
client_port.open("/demo/client");
// connect to server
if (!yarp.connect("/demo/client",servername.c_str()))
{
std::cout << "Error! Could not connect to server " << servername << std::endl;
return -1;
}
// Instatate proxy object and attach it to the port -- the proxy will use this port to talk to the server
Demo demo;
demo.yarp().attachAsClient(client_port);
// Now we are ready to chat with the server!
// Notice that from now on we will invoke only the server methods declared in demo.thrift/Demo.h
std::cout << "Hey are you up and running?" << std::endl;
while(!demo.is_running())
{
std::cout << "No? Well... start!" << std::endl;
demo.start();
}
std::cout << "Wonderful! I have a question for you... so, what's the answer??" << std::endl;
int32_t answer=demo.get_answer();
std::cout << "What?? " << answer << "?? Are you kidding??";
answer = demo.add_one(answer);
std::cout << " It's definitely " << answer << "!!" << std::endl;
demo.set_answer(answer);
std::cout << "Got it? So, repeat after me: the answer is ... " << demo.get_answer() << "! Great!" << std::endl;
std::cout << "Ok you can relax now, I'll leave you alone" << std::endl;
demo.stop();
std::cout<<"Bye" << std::endl;
return 0;
}

Implementing the server as a RFModule

A nicer altenative is to create a YARP RFModule that also implements the service interface:

class DemoServerModule : public Demo, public yarp::os::RFModule
{
// Members declaration and service functions implementation
// <snip> see above
// RFModule functions declaration
bool attach(yarp::os::Port &source);
bool updateModule();
bool close();
};

The RFModule implementation:

bool DemoServerModule::attach(yarp::os::Port &source)
{
return this->yarp().attachAsServer(source);
}
bool DemoServerModule::configure( yarp::os::ResourceFinder &rf )
{
std::string moduleName = rf.check("name",
yarp::os::Value("demoServerModule"),
"module name (string)").asString().c_str();
setName(moduleName.c_str());
std::string slash="/";
attach(cmdPort);
std::string cmdPortName= "/";
cmdPortName+= getName();
cmdPortName += "/cmd";
if (!cmdPort.open(cmdPortName.c_str())) {
std::cout << getName() << ": Unable to open port " << cmdPortName << std::endl;
return false;
}
return true;
}
bool DemoServerModule::updateModule()
{
// do something very useful
return true;
}
bool DemoServerModule::close()
{
cmdPort.close();
return true;
}

And the main function:

// Check YARP and run the module
int main(int argc, char *argv[])
{
if (!yarp.checkNetwork())
{
std::cout<<"Error: yarp server does not seem available"<<std::endl;
return -1;
}
rf.configure(argc, argv);
DemoServerModule demoMod;
if (!demoMod.configure(rf)) {
return -1;
}
return demoMod.runModule();
}

Complete example

A complete example of Thrift code generation and server/client creation with CMake is available in example/idl/thriftSimple

This is what we get when we launch server and client from command line (assuming a yarpserver is running, and we are in the "build" directory):

Client

Server

$ ./demoClient --server /demoServerModule/cmd
yarp: Port /demo/client active at tcp://10.255.36.53:10003
$ ./demoServerModule
||| policy set to YARP_POLICY
||| YARP_POLICY:
      <snip>
I know the answer!
yarp: Port /demoServerModule/cmd active at tcp://10.255.36.53:10002

yarp: Sending output from /demo/client to /demoServerModule/cmd using tcp
Hey are you up and running?
No? Well... start!
yarp: Receiving input from /demo/client to /demoServerModule/cmd using tcp
Indeed I am not running
Starting!
Indeed I am running

Wonderful! I have a question for you... so, what's the answer??
What?? 42?? Are you kidding?? It's definitely 43!!
The answer is 42
I'm adding one to 42. That's easy :)
OMG are you serious? The answer is 43?!?

Got it? So, repeat after me: the answer is ... 43! Great!
Ok you can relax now, I'll leave you alone
The answer is 43
Stopping!

Bye
yarp: Removing output from /demo/client to /demoServerModule/cmd
yarp: Removing input from /demo/client to /demoServerModule/cmd

RPC calls can also be sent to the server from command line (note that commands with "_" in their name can be split):

$ yarp rpc /demoServerModule/cmd
get_answer
Response: 42
set_answer 28
Response: [ok]
get answer
Response: 28
is running
Response: [fail]
start
Response: [ok]
is running
Response: [ok]
stop
Response: [ok]
is_running
Response: [fail]
yarp::os::Port::close
void close() override
Stop port activity.
Definition: Port.cpp:357
yarp::os::RFModule
A base-class for standard YARP modules that supports ResourceFinder.
Definition: RFModule.h:24
yarp::os::PortReader::read
virtual bool read(ConnectionReader &reader)=0
Read this object from a network connection.
all.h
main
int main(int argc, char *argv[])
Definition: yarpros.cpp:261
yarp::os::Property::find
Value & find(const std::string &key) const override
Gets a value corresponding to a given keyword.
Definition: Property.cpp:1034
yarp::os::Port::open
bool open(const std::string &name) override
Start port operation, with a specific name, with automatically-chosen network parameters.
Definition: Port.cpp:82
yarp::os::Port
A mini-server for network communication.
Definition: Port.h:50
yarp::os::RFModule::updateModule
virtual bool updateModule()=0
Override this to do whatever your module needs to do.
yarp::os::Property::fromCommand
void fromCommand(int argc, char *argv[], bool skipFirst=true, bool wipe=true)
Interprets a list of command arguments as a list of properties.
Definition: Property.cpp:1057
yarp::os::ResourceFinder::configure
bool configure(int argc, char *argv[], bool skipFirstArgument=true)
Sets up the ResourceFinder.
Definition: ResourceFinder.cpp:803
yarp::os::RFModule::attach
virtual bool attach(yarp::os::Port &source)
Make any input from a Port object go to the respond() method.
Definition: RFModule.cpp:459
yarp::os::Value::asString
virtual std::string asString() const
Get string value.
Definition: Value.cpp:237
yarp::os::Wire
Base class for IDL client/server.
Definition: Wire.h:22
yarp::os::RFModule::configure
virtual bool configure(yarp::os::ResourceFinder &rf)
Configure the module, pass a ResourceFinder object to the module.
Definition: RFModule.cpp:441
yarp::os::ResourceFinder::check
bool check(const std::string &key) const override
Check if there exists a property of the given name.
Definition: ResourceFinder.cpp:920
yarp::os::mkdir
int mkdir(const char *p)
Portable wrapper for the mkdir() function.
Definition: Os.cpp:40
yarp::os::ConnectionReader
An interface for reading from a network connection.
Definition: ConnectionReader.h:40
yarp::os::Wire::yarp
yarp::os::WireLink & yarp()
Get YARP state associated with this object.
Definition: Wire.h:34
yarp::os
An interface to the operating system, including Port based communication.
Definition: AbstractCarrier.h:17
yarp::os::Network
Utilities for manipulating the YARP network, including initialization and shutdown.
Definition: Network.h:786
yarp
The main, catch-all namespace for YARP.
Definition: environment.h:18
yarp::os::Value
A single value (typically within a Bottle).
Definition: Value.h:47
yarp::os::Time::delay
void delay(double seconds)
Wait for a certain number of seconds.
Definition: Time.cpp:114
slash
constexpr fs::value_type slash
Definition: Run.cpp:100
yarp::os::Property
A class for storing options and configuration information.
Definition: Property.h:37
yarp::os::ResourceFinder
Helper class for finding config files and other external resources.
Definition: ResourceFinder.h:33
yarp::os::RFModule::close
virtual bool close()
Close function.
Definition: RFModule.cpp:493