YARP
Yet Another Robot Platform
Thrift IDL in YARP: writing a portable

YARP built-in types can be sent through Ports.

Sometimes you need to send custom datatypes, in YARP objects that can be sent through ports are called Portable objects. Port Power, Going Further with Ports shows how you can manually implement a portable object. This tutorial shows how you can automate this process using the Thrift compiler.

Introduction

Suppose we have two modules that wish to share a SharedData struct that contains a string and a vector. Normally you would define the data in a C++ header file and write serialization/deserialization methods to allow it to be sent and received to/from a port (this process is called marshalling/demarshalling). This process requires some YARP specific expertise, is error-prone and tedious. The idea here is that instead of manually writing the class you define the structure using an intermediate language (the Thrift IDL language), then you ask a compiler (the yarpidl_thrift compiler) to generate the class for you, including all the required code.

Let's see how to do it.

Thrift definition for <tt>SharedData</tt>

We start by defining our ShareData structure. Open a text editor and type the following:

struct SharedData {
1: string text;
2: list<double> content;
}

name this file SharedData.thrift save and close it.

In thrift's syntax this specifies that SharedData is a struct that contains a text field of type string and a content field which type is a vector of floats. Thrift in fact supports much more options, see Thrift IDL in YARP: advanced tutorial.

Now the yarpidl_thrift compiler can be invoked to generate both SharedData.h and SharedData.cpp.

Type:

yarpidl_thrift --gen yarp --out ./ SharedData.thrift

This will generate two files: SharedData.h and SharedData.cpp.

In case you are interested you can inspect them to see that they define a C++ class with some extra code. The good thing is that you don't actually need to bother about the details, but you can readily use the class in your code.

Now we can use these files in a YARP program.

Code

This code is straightforward. We define a simple sender executable that open a port and periodically write a SharedData object.

As usual we start with the CMake code, write your CMakeLists.txt:

cmake_minimum_required(VERSION 3.12)
find_package(YARP COMPONENTS os sig REQUIRED)
add_executable(sender)
target_sources(sender PRIVATE sender.cpp SharedData.cpp)
target_include_directories(sender PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(sender PRIVATE YARP::YARP_os
YARP::YARP_sig
YARP::YARP_init)

Now we write the code for the sender in sender.cpp:

#include "SharedData.h"
#include <iostream>
#include <yarp/os/Time.h>
using namespace std;
int main()
{
if (!port.open("/sender"))
{
cerr<<"Error opening port, check your yarp network\n";
return -1;
}
cout<<"Starting sender\n";
while(true)
{
SharedData d;
// d.text is a string
d.text="Hello from sender";
//d.content is a vector, let's push some data
d.content.push_back(0.0);
d.content.push_back(0.0);
port.write(d);
}
return 0;
}

Now compile the code

mkdir build
cd build
cmake ../
make
./sender

Now you can run yarp read on a separate console to inspect the content that is being transmitted on the port, the output should be something like this:

yarp read ... /sender
yarp: Port /tmp/port/1 active at tcp://127.0.0.1:10003
yarp: Receiving input from /sender to /tmp/port/1 using tcp
"Hello from sender" (0.0 0.0)
"Hello from sender" (0.0 0.0)

It is simple to write a receiver:

Append the following to the CMakeLists.txt

add_executable(receiver)
target_sources(receiver PRIVATE receiver.cpp
SharedData.cpp)
target_link_libraries(receiver PRIVATE YARP::YARP_os
YARP::YARP_sig
YARP::YARP_init)

This is the receiver code in receiver.cpp:

#include <SharedData.h>
#include <iostream>
using namespace std;
int main()
{
cout<<"Starting receiver\n";
if (!port.open("/receiver"))
{
cerr<<"Error opening port, check your yarp network\n";
return -1;
}
while(true)
{
SharedData d;
port.read(d);
//access d
}
return 0;
}

Using CMake

YARP provides CMake supports to automate the invocation of yarpidl_thrift. This is convenient in large projects when we generate several files and we do not want to keep track of all of them individually.

We can use yarp_idl_to_dir to tell CMake to parse SharedData.thrift with the thrift compiler. All generated files will be placed in separate include and src directories.

# 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 SharedData.thrift
OUTPUT_DIR ${generated_libs_dir}
SOURCES_VAR sources
HEADERS_VAR headers
INCLUDE_DIRS_VAR include_dirs)
# create the sender
add_executable(sender)
target_sources(sender PRIVATE sender.cpp
${headers}
${sources})
target_include_directories(sender PRIVATE ${include_dirs})
target_link_libraries(sender PRIVATE YARP::YARP_os
YARP::YARP_sig
YARP::YARP_init)
# create the receiver
add_executable(receiver)
target_sources(receiver PRIVATE receiver.cpp
${headers}
${sources})
target_include_directories(receiver PRIVATE ${include_dirs})
target_link_libraries(receiver PRIVATE YARP::YARP_os
YARP::YARP_sig
YARP::YARP_init)

Now you just have to execute CMake. The variable ALLOW_IDL_GENERATION controls if the thrift compiler is executed to generate SharedData.h/cpp or not. It will be off by default if there is already generated code in the desired directory, you'll need to turn it on to overwrite that.

cd build
cmake ../ -DALLOW_IDL_GENERATION=TRUE

The code generation step is required only when SharedData.thrift is modified.

Related Tutorials

The Thrift IDL also allows defining modules interfaces, this is explained here:

YARP supports ROS types. An alternative method to define portable objects in YARP is to use the ROS syntax (i.e. through a .msg file). The disadvantage of this approach is that you cannot use native YARP types, but the advantage is that your messages will be compatible with ROS and readable from a topic. This tutorial shows how you do this:

Code for this tutorial can be found here: example/idl/portable

Network.h
main
int main(int argc, char *argv[])
Definition: yarpros.cpp:261
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
BufferedPort.h
yarp::os::Port::read
bool read(PortReader &reader, bool willReply=false) override
Read an object from the port.
Definition: Port.cpp:475
yarp::os::Port::write
bool write(const PortWriter &writer, const PortWriter *callback=nullptr) const override
Write an object to the port.
Definition: Port.cpp:430
yarp::os::Network
Utilities for manipulating the YARP network, including initialization and shutdown.
Definition: Network.h:786
Time.h
yarp::os::Time::delay
void delay(double seconds)
Wait for a certain number of seconds.
Definition: Time.cpp:114