YARP
Yet Another Robot Platform
ROS Types in YARP: sending data to ROS

This tutorial assumes that you have completed the steps in ROS Types in YARP: writing a portable.

That tutorial showed how to use types defined in a ROS-style .msg file within YARP. Now, we will work on sending data from YARP to ROS.

At the beginning of this tutorial, we assume you have a YARP server running, without any special configuration. During the tutorial, we will reconfigure the name server to communicate with ROS.

Making the sender program ROS-compatible

The sender program from ROS Types in YARP: writing a portable looks like this:

#include <yarp/rosidl/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";
double count=0.0;
while(true)
{
yarp::rosidl::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(count++);
d.content.push_back(count++);
port.write(d);
}
return 0;
}

This program was written using a yarp::os::Port, a very flexible creature. It can communicate with ROS, if we tame it a little bit to fit in with ROS's expectations that promise to never change the type of data sent, and promise to always send data in a particular direction. We can make those promises by calls to yarp::os::Port::setWriteOnly and yarp::os::Port::promiseType, or we can switch to using the helper class yarp::os::Publisher that is already correctly configured. We can also add a yarp::os::Node to make ROS even happier.

/*
* Copyright (C) 2006-2020 Istituto Italiano di Tecnologia (IIT)
* All rights reserved.
*
* This software may be modified and distributed under the terms of the
* BSD-3-Clause license. See the accompanying LICENSE file for details.
*/
#include <yarp/rosmsg/SharedData.h>
#include <iostream>
#include <yarp/os/Node.h>
#include <yarp/os/Time.h>
using namespace std;
int main()
{
yarp::os::Node node("/sender/node"); // added a Node
yarp::os::Publisher<yarp::rosmsg::SharedData> port; // changed Port to Publisher
if (!port.topic("/data")) // replaced open() with topic()
{
cerr<<"Error opening port, check your yarp network\n";
return -1;
}
cout<<"Starting sender\n";
double count=0.0;
while(true)
{
yarp::rosmsg::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(count++);
d.content.push_back(count++);
port.write(d);
}
return 0;
}

Great, at this point our port is nice and tame by ROS standards. And the code will still work just fine without ROS. For example, we can run our existing receiver program from ROS Types in YARP: writing a portable. We're now using a topic called /data so after running the receiver you'll need to subscribe it to the topic:

yarp connect topic://data /receiver

We can also update the receiver code to use the yarp::os::Subscriber helper class:

/*
* Copyright (C) 2006-2020 Istituto Italiano di Tecnologia (IIT)
* All rights reserved.
*
* This software may be modified and distributed under the terms of the
* BSD-3-Clause license. See the accompanying LICENSE file for details.
*/
#include <yarp/rosmsg/SharedData.h>
#include <iostream>
#include <yarp/os/Node.h>
using namespace std;
int main()
{
cout<<"Starting receiver\n";
yarp::os::Node node("/receiver/node"); // added a Node
yarp::os::Subscriber<yarp::rosmsg::SharedData> port; // changed Port to Subscriber
if (!port.topic("/data")) // replaced open() with topic()
{
cerr<<"Error opening port, check your yarp network\n";
return -1;
}
// network.connect("/sender", "/receiver"); // don't need this anymore
int count=0;
while(true)
{
yarp::rosmsg::SharedData d;
port.read(d);
//access d
cout << count << " Received SharedData:\n";
cout << d.text << "\n";
for (int i=0; i<d.content.size(); i++)
{
cout<<d.content[i]<<" ";
}
cout<<"\n";
count++;
}
return 0;
}

Registering with ROS

Now let's make our programs visible with ROS. Stop running them, and then also stop the yarp name server. Make sure you have a ROS name server running ("roscore"/"rosmaster"), and that the ROS_MASTER_URI environment variable is correctly set. Then run the yarp name server with the –ros flag:

$  yarpserver --ros
... start-up messages omitted ..
Using ROS with ROS_MASTER_URI=http://127.0.0.1:11311
... start-up messages omitted ..
Ok.  Ready!

Done! Now, if we rerun the sender and receiver programs, they should work as before. The (invisible) difference is that they are being connected by ROS rather than YARP. A visible difference is that the programs are now visible within ROS. Try:

$  rostopic list
...
/data
...
$  rosnode list
...
/receiver/node
/sender/node
...
$  rostopic info /data
Type: yarp/SharedData

Publishers:
 * /sender/node (http://192.168.1.3:10005)

Subscribers:
 * /receiver/node (http://192.168.1.3:10003)
$  rosnode info /receiver/node
Node [/receiver/node]
Publications: None

Subscriptions:
 * /data [yarp/SharedData]

Services: None

contacting node http://192.168.1.3:10003 ...
Pid: [12432]

Notice that ROS is reporting the topic /data as having the type yarp/SharedData. ROS needs types to include a "package" name, and we haven't specified one yet, so YARP has added a fake one. To get full interoperation, we now need to add our SharedData type in ROS (not just YARP), and as part of that process we'll end up making a real ROS package.

Making the .msg file available to ROS

In ROS Types in YARP: writing a portable we defined a SharedData structure in a SharedData.msg file and used it to communicate between sender and receiver programs written using YARP. Now we need to make that file visible to ROS. ROS expects .msg files to be in a directory called msg within a ROS "package". If you already have a package set up for your work, you can just use that. For completeness, we give a very minimal ROS package here, just enough to experiment with, but you can also just follow ROS tutorials, there's nothing YARP-specific here. Do the following:

  • Create an empty directory, called say yarpros_tutorial.
  • Within that directory, create another empty directory called src.
  • Within that directory, create another empty directory that will be our package, called say yarp.
  • Within yarp, create an empty directory called msg.
  • Copy SharedData.msg into yarpros_tutorial/src/yarp/msg/.
  • Create a file called yarpros_tutorial/src/yarp/package.xml and place the following in it:
<?xml version="1.0"?>
<package>
<name>yarp</name>
<version>0.0.0</version>
<description>A test yarp package</description>
<maintainer email="paul@robotrebuilt.com">Paul Fitzpatrick</maintainer>
<license>BSD-3-Clause</license>
<build_depend>message_generation</build_depend>
<run_depend>message_runtime</run_depend>
<buildtool_depend>catkin</buildtool_depend>
</package>
  • Create a file called yarpros_tutorial/src/CMakeLists.txt and place the following in it:
cmake_minimum_required(VERSION 3.12)
project(yarp)
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation)
add_message_files(FILES SharedData.msg)
generate_messages(DEPENDENCIES std_msgs)
catkin_package(CATKIN_DEPENDS message_runtime)
  • While in the yarpros_tutorial directory, run catkin_make. You should see something like:
...
-- +++ processing catkin package: 'yarp'
...
Generating C++ code from yarp/SharedData.msg
[ 50%] Generating Python from MSG yarp/SharedData
...
[100%] Built target yarp_generate_messages

Now we need to tell ROS about the existence of yarpros_tutorial. There are many ways to do that. In a bash shell, the simplest is to do (from the yarpros_tutorial directory):

$ source devel/setup.bash

At this point, the rosmsg command should be able to find our type:

$  rosmsg show yarp/SharedData
string text
float64[] content

And if we run the sender program, we can print it from ROS:

$  rostopic echo /data
text: Hello from sender
content: [4794.0, 4795.0]
---
text: Hello from sender
content: [4796.0, 4797.0]
---
text: Hello from sender
content: [4798.0, 4799.0]
---
text: Hello from sender
content: [4800.0, 4801.0]
---

If we stop the sender program and run the receiver, we can publish to if from ROS. If we do this:

rostopic pub /data yarp/SharedData "hello from ROS" "[1.1,2.2,3.3]"

The receiver program prints this:

0 Received SharedData:
hello from ROS
1.1 2.2 3.3
Network.h
Subscriber.h
yarp::os::Subscriber::read
T * read(bool shouldWait=true)
Read a message from the port.
Definition: Subscriber.h:117
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::Publisher
A port specialized for publishing data of a constant type on a topic.
Definition: Publisher.h:26
yarp::os::Port
A mini-server for network communication.
Definition: Port.h:50
yarp::os::Publisher::topic
bool topic(const std::string &name)
Set topic to publish to.
Definition: Publisher.h:67
yarp::os::Publisher::write
void write(bool forceStrict=false)
Write the current object being returned by Publisher::prepare.
Definition: Publisher.h:152
std_msgs
Definition: Bool.h:22
Node.h
yarp::os::Node
The Node class.
Definition: Node.h:27
BufferedPort.h
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
Publisher.h
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::Subscriber
A port specialized for reading data of a constant type published on a topic.
Definition: Subscriber.h:26
Time.h
yarp::os::Time::delay
void delay(double seconds)
Wait for a certain number of seconds.
Definition: Time.cpp:114
yarp::os::Subscriber::topic
bool topic(const std::string &name)
Set topic to subscribe to.
Definition: Subscriber.h:66