YARP ROS Interoperation Past and Future But Not Now

From Wiki for iCub and Friends
Jump to: navigation, search

This page gives historical steps in YARP/ROS interoperation, and is occasionally used during development of new features. You probably want http://wiki.icub.org/yarpdoc/yarp_with_ros.html which gives the current steps needed for interoperation.

This Is Not the Page You Are Looking For


ROS/YARP compatibility work has proceeded as follows:

  • Phase 1 - wire support
  • Phase 2 - name support
  • Phase 3 - node support
    • Add ROS-like nodes to YARP.
    • network form nodes written with ROS.

Setup: Compiling YARP to support ROS

You'll need to turn the following flags on in CMake before (re-)compiling YARP:

* CREATE_OPTIONAL_CARRIERS (for support of ROS wire protocols)
* CREATE_IDLS (for support of ROS .msg/.srv files)

Those flags in turn create extra options, and the following should be turned on:

* ENABLE_yarpcar_tcpros_carrier (the usual protocol for topics)
* ENABLE_yarpcar_rossrv_carrier (tcpros with minor tweaks for services)
* ENABLE_yarpcar_xmlrpc_carrier (used by ros nameserver and node interfaces)
* ENABLE_yarpidl_rosmsg (a program to convert .msg/.srv files into YARP-usable form)

At some point, all these will become available by default, but that is not yet the case.

Setup: directing YARP clients to use the ROS name-server

To start with, it is simplest to configure YARP clients to use the ROS name server. Stop any yarpserver process you may have, and do the following:

  # optional - this avoids overwriting "normal" yarp configuration files
  yarp namespace /ros
  # find ROS name server, respecting ROS_MASTER_URI
  yarp detect --ros --write  

Just to describe in a little detail what happens here. YARP has a little configuration file (stored in the location reported by yarp conf) that lets all YARP-using programs know how to contact any name servers they need. For a ROS name server running on port 11311 (the default for ROS) on a machine with IP address, the configuration file should hold the following: 11311 ros

The "ros" tag tells the client what protocol to speak (if this is missing, a yarp-native protocol will be used by default). If the standard ROS_MASTER_URI environment variable is set correctly, then yarp detect --ros --write should prepare exactly this configuration for you.

One more step is desirable. ROS has its own type-system that depends on .msg/.srv files scattered around a ROS installation. To make those types available to YARP when needed, without having to have a working ROS installation on all machines, we can run a special "type server" on any one machine with a ROS installation:

  # give access to ROS type info to ROS-less machines
  yarpidl_rosmsg --name /typ@/yarpros

In fact, you can run this server on a machine *without* a ROS installation, and it will fall back on interrogating the http://docs.ros.org website for type information. This will work for types used in ROS packages documented on that site.

Trying things out: reading ROS topics

Let's start with a simple test reader:

 yarp read /msg@/test_node

For someone familiar with ROS, the "/msg@/test_node" syntax can be read as "use a topic name of /msg, and a node name of /test_node". We can check that everything looks familiar with rosnode and rostopic:

 $ rosnode list
 $ rostopic info /msg
 Type: unknown type
 Publishers: None
 * /test_node (... address ...)

As invoked, we didn't assign any particular type to the port/topic - this can be picked up from the sender. Try any of:

 rostopic pub /msg std_msgs/String "hello yarp"
 rostopic pub /msg turtlesim/Pose 0 5 10 15 20  

And you should see the messages print out on yarp read's console.

Trying things out: publishing to ROS topics

Let's start a ROS program to print out strings:

  $ rosrun roscpp_tutorials listener 

This program subscribes to a topic called "/chatter". Let's publish something there from YARP:

  $ yarp write /chatter@/yarp_writer
  yarp: Port /yarp_writer active at tcp://
  yarp: Port /chatter+@/yarp_writer active at tcp://
  yarp: Sending output from /chatter+@/yarp_writer to /listener using tcpros

Once we type a message (here "hello?") and hit return, we should see it echoed on the listener's console:

  [ INFO] [1386605949.838711935]: I heard: [hello?]

Under the hood, the yarp port has found the type of data expected (from the listener) and is matching what you enter with that type. If you try to send a number, you'll get a message like this:

  yarp: Structure of message is unexpected (expected std_msgs/String)

(If you actually want to send a string that looks like an integer, just put it in quotes). In this case, the listener is expecting a message of type std_msgs/String.

Trying things out: using ROS services

Let's start a ROS service that adds two integers:

 rosrun rospy_tutorials add_two_ints_server

This creates a service named /add_two_ints that expects two integers and gives back an integer. We can use it from yarp rpc, for example:

 $ yarp rpc /add_two_ints
 22 20
 Response: 42
 1 -10
 Response: -9

This looks straightforward, but it relies critically on YARP being able to determine ROS types. If you were to shut down the yarpidl_rosmsg server, here is what you would see:

 $ yarp rpc /add_two_ints
 Do not know anything about type 'rospy_tutorials/AddTwoInts'
 Could not connect to a type server to look up type 'rospy_tutorials/AddTwoInts'

With the type server running, YARP can make the needed translations. Let's try the opposite direction, setting up a ROS-style rpc server, for example this:

 $ yarp rpcserver /add_two_ints@/my_int_server --type rospy_tutorials/AddTwoInts

Notice that we specify a node name for the server here, and also we give the type of data it expects and returns (ROS-style clients expect us to know that). Now from ROS we can do:

 $ rosrun roscpp_tutorials add_two_ints_client 3 4

On the "yarp rpcserver" terminal we will now see:

 Waiting for a message...
 yarp: Receiving input from /add_two_ints_client to /add_two_ints-1@/my_int_server using tcpros
 Message: 3 4

It is waiting for us to reply. We can type in whatever number we like ("7" if we are feeling constructive), and it will be reported on the "roscpp_tutorials" terminal:

 [ INFO] [1386793386.003997149]: Sum: 7

Trying things out: sending/receiving simple messages from code

Please see "example/ros" in the YARP source code for full examples. For simple cases, we can just use YARP Bottles whose content matches ROS types. For example, to call a ROS service that adds two integers, we could do this (error checking abbreviated, see example/ros directory for full code):

 #include <stdio.h>
 #include <stdlib.h>
 #include <yarp/os/all.h>
 using namespace yarp::os;
   if (argc!=3) return 1; // expect two integer arguments
   Network yarp;
   RpcClient client;
   if (!client.open("/add_two_ints@/yarp_add_int_client")) return 1;
   Bottle msg, reply;
   if (!client.write(msg,reply)) return 1;
   printf("Got %d\n", reply.get(0).asInt());
   return 0;

An example CMakeLists.txt file to compile this and link with YARP would be:

 cmake_minimum_required(VERSION 2.8.7)
 find_package(YARP REQUIRED)
 add_executable(add_int_client_v1 add_int_client_v1.cpp)
 target_link_libraries(add_int_client_v1 ${YARP_LIBRARIES})

On the ROS side we'd do:

 rosrun rospy_tutorials add_two_ints_server

Then on the YARP side we can try it out (assume the above program is compiled as add_int_client_v1):

 $ ./add_int_client_v1 4 6
 yarp: Port /yarp_add_int_client active at tcp://
 yarp: Port /add_two_ints+1@/yarp_add_int_client active at tcp://
 Got 10

Now looking at the code, there are some things to note. We use an RpcClient. This is a regular yarp Port, configured for writing to a single target. Equivalently we could have used this:

 Port client;

If we try to use a regular port without telling YARP how we'll be using it (by calling one of setRpcClient(), setRpcServer(), setWriteOnly(), setReadOnly()), YARP will complain because it won't know how to describe it to ROS. However, if you have a YARP-using program that you can't easily modify to add such a call, you can sneak the needed information into the port name.

ROS idea Call needed to specialize a Port/BufferedPort Or add this decoration before "@" in port name Pre-specialized class
Topic Subscriber port.setWriteOnly() - (none)
Topic Publisher port.setReadOnly() + (none)
Service port.setRpcServer() -1 yarp::os::RpcServer
Client port.setRpcClient() +1 yarp::os::RpcClient

For new code, it may be convenient to create ROS-like nodes explicitly rather than having names that bundles node and topic/service names together. YARP now has a yarp::os::Node class that can be used like this (modifying our example for adding two integers):

 #include <stdio.h>
 #include <stdlib.h>
 #include <yarp/os/all.h>
 using namespace yarp::os;
   Network yarp;
   Node node("/yarp_add_int_client");
   RpcClient client;
   if (!client.open("add_two_ints")) return 1;  // names that omit leading "/" belong to node
   Bottle msg, reply;
   return 0;

Here's a YARP equivalent of the ROS listener/talker tutorial:

 // listener
 #include <stdio.h>
 #include <yarp/os/all.h>
 using namespace yarp::os;
   Network yarp;
   Node node("/yarp/listener");
   Port port;
   if (!port.open("chatter")) return 1;
   while (true) {
     Bottle msg;
     if (!port.read(msg)) continue;
     printf("Got [%s]\n", msg.get(0).asString().c_str());
   return 0;
 // talker
 #include <stdio.h>
 #include <yarp/os/all.h>
 using namespace yarp::os;
   Network yarp;
   Port port;
   if (!port.open("/chatter@/yarp/talker")) return 1;
     char buf[256];
     sprintf(buf,"hello ros %d", i);
     Bottle msg;
     printf("Wrote: [%s]\n", buf);
   return 0;

One thing to watch out for is that if you stop a program using ^C or if it crashes, YARP will not yet unregister your ports with ROS. If this bothers you, either add a signal handler, or run "rosnode cleanup" from time to time - and my apologies.

Trying things out: sending/receiving complicated messages from code

When dealing with larger, more complex messages (e.g point clouds), Bottles get awkward and it is desirable to be able to access parts of the message in a more structured way. For this case, you can use the yarpidl_rosmsg program. We met this program before when we ran it as a type server. We can also use it as a utility to translate ROS .msg/srv files into YARP-compatible header files. Given a .msg file (or a type name that rosmsg can find), yarpidl_rosmsg will produce a C++ class that implements the yarp::os::Portable interface and so can be written to or read from a YARP port. Likewise for a .srv file. If the file uses nested types, a header file will be generated for each type needed.

As a very simple example, to translate the AddTwoInts type we've been using in examples, we could do:

  yarpidl_rosmsg --web true rospy_tutorials/AddTwoInts

(I use --web true because I'm testing on a machine without a full ROS install; if you have all the needed packages then that could be omitted). This would generate the following header file:

 class rospy_tutorials_AddTwoInts : public yarp::os::Portable {
   // ...
   yarp::os::NetInt64 a;
   yarp::os::NetInt64 b;
   bool read(yarp::os::ConnectionReader& connection) { ... }
   bool write(yarp::os::ConnectionWriter& connection) { ... }

Note we can directly access to 64-bit integers, a and b, and have read/write serialization methods that YARP ports can use. We also get a class for the reply:

 class rospy_tutorials_AddTwoIntsReply : public yarp::os::Portable {
   // ...
   yarp::os::NetInt64 sum;

In code, we could use an AddTwoInts service as follows:

 rospy_tutorials_AddTwoInts msg;
 rospy_tutorials_AddTwoIntsReply reply;
 msg.a = 20;
 msg.b = 22;
 printf("Sum: %d\n", reply.sum);  // should print 42

Code generation can be automated using the yarp_idl_to_dir macro, for example:

 set(generated_libs_dir "${CMAKE_CURRENT_BINARY_DIR}")
 yarp_idl_to_dir(Demo.msg ${generated_libs_dir})

See src/idls/rosmsg/tests/demo/ in the YARP source code for examples of usage.

Phase 3

Prerequisites - YARP configured with these flags turned on in CMake:



* ENABLE_yarpcar_tcpros_carrier
* ENABLE_yarpcar_rossrv_carrier
* ENABLE_yarpcar_xmlrpc_carrier
* ENABLE_yarpidl_rosmsg



If a port name contains the "@" character, then that name is interpreted as a node-name and topic/service-name pair. For example:

   yarp read /msg@/reader

will create a node called "/reader" that subscribes to a topic called "/msg". On a machine with ROS installed, you can check this with:

   rosnode list
   rosnode info /reader
   rostopic list
   rostopic info /msg

We could send a message to this port from ROS (assuming we are using a ROS nameserver) as follows:

   rostopic pub /msg std_msgs/String "hello world"


  yarp write /msg@/writer

will create a node called "/writer" that publishes to a topic called "/msg". For this to communicate with ROS programs, we'd also need to specify a type, for example:

  yarp write /msg@/writer --type std_msgs/String

YARP has a direct understanding of a small number of ROS types, but for more it needs a "type server" to run on a computer with ROS installed. Run this (the "/typ" service name is important, the node name is not):

  yarpidl_rosmsg --name /typ@/ros/type/server

The following demos should now work:

  # DEMO: image stream from YARP to ROS
  # Should see red line scrolling down image_view viewer
  yarpdev --device test_grabber --name /test_grabber=/camera/image
  rosrun image_view image_view image:=/camera/image
  # DEMO: image stream from ROS to YARP
  # I just wrote a quick program...
  yarpview /whatever/the/image/topic/is@/yarpview
  # DEMO: basic read/write
  rosrun rospy_tutorials listener
  yarp write /chatter@/ros/check/write --type std_msgs/String
  # DEMO: basic write/read
  rosrun rospy_tutorials talker
  yarp read /chatter@/ros/check/read
  # DEMO: manipulating turtle demo
  rosrun turtlesim turtlesim_node
  yarp read /turtle1/pose@/foo  # should show a stream of poses
  yarp rpc /turtle1/teleport_absolute # give x y theta e.g. "6.0 3.0 0.5"
  yarp rpc /reset  # hit enter to send a blank message and do reset

Possible examples for an API for when writing new code:

  Node node("/producer");
  // "data" is written to just like a BufferedPort
  Node node("/consumer");
  Subscriber<Vector> data("readings");
  // "data" is read from like a BufferedPort

Phase 2



ROS vs roscore


Using YARP and ROSCORE in the same program






Using YARP and ROSCORE on the same network



The XML/RPC carrier




* XML/RPC integer <-> Bottle integer
* XML/RPC string <-> Bottle string
* XML/RPC blobs <-> Bottle blobs
* XML/RPC array <-> Bottle list
* XML/RPC structure <-> Bottle list


 (dict (key1 val1) (key2 val2))



 yarp name register /roscore tcp zimba 11311
 # send a look-up message, following the master API

This result is printed:




The TCPROS carrier








 yarp name register /add_two_ints_server tcp contact 37291
 [prints] 1 "" (TCPROS contact 38265)
 yarp name register /adder tcp contact 38265




Now to connect it up we would do something like:

 yarp name register /talker tcp contact 37291
 [prints] 1 "" (TCPROS contact 38265)
 yarp name register /talker/chatter tcp contact 38265




yarpros roscore <hostname> <port number>
 -- tell yarp how to reach the ros master
 -- example: yarpros roscore 11311
 yarpros import <name>
 -- import a ROS name into YARP
 -- example: yarpros import /talker
 yarpros read <yarpname> <nodename> <topicname>
 -- example: yarpros read /read /talker /chatter
 yarpros write <yarpname> <nodename> <topicname>
 -- example: yarpros write /write /listener /chatter
 yarpros rpc <yarpname> <nodename> <servicename>
 -- example: yarpros rpc /rpc /add_two_ints_server /add_two_ints


Image carrier


















The basic logic is that YARP-using programs will "register everywhere and query anywhere":

  • ROS will ignore yarpserver (since it knows nothing about it). So all non-YARP programs will be registered with roscore only.
  • YARP-using programs will register with *both* yarpserver and roscore.
  • YARP-using programs, when doing queries, with check with each nameserver in whatever order they are listed when you did "yarp namespace /n1 /n2". It will return the first valid response found.


    • Ports without replies
    • Ports with replies