Skip to content

ROS1 Cpp Interfaces

Francesco Ganci edited this page Jun 26, 2022 · 12 revisions

Initial Remarks

⚠️ to complete ⚠️

  • I'm referring especially to the ROS1 simple node pattern, but if you need a class patter for the node, few updates are needed.

Naming Conventions - ROScpp nodes

⚠️ work in progress, see the code ⚠️

  • a topic name as a macro TOPIC_??? replacing ??? with the uppercase name of the topic
  • for queue size for one topic, Q_SZ_??? replacing ??? with the uppercase name of the topic

✉️ ROS1 C++ topics

Global Definitions

suppose you want to use the message msg inside the package pkg. The topic is simply /top; the NodeHandle object is nh.

// include the message type (pkg::msg)
#include <pkg/msg.h>

// name of the topic and queue size
#define TOPIC_TOP "/top"
#define Q_SZ_TOP 1000

open a publisher

Some remarks:

  • the main call is ros::Publisher p = nh.advertize<pkg::msg>( "topic", queue_sz ) which takes as input the type of the message, the topic (as std::string) and the queue size (usually 1000)
// define globally as pointer
ros::Publisher *pub_top;

// in the main function ... 
TLOG( "Creating publisher " << LOGSQUARE( TOPIC_TOP ) << "..." );
ros::Publisher tpub_top = nh.advertise<pkg::msg>( TOPIC_TOP, Q_SZ_TOP );
pub_top = &tpub_top;
TLOG( "Creating publisher " << LOGSQUARE( TOPIC_TOP ) << "... OK" );

Use a publisher

the main call is pub_topic->publish( m ) which simply publishes the message through the topic.

pkg::msg m;

// fill in the message
// m.field = value;

pub_topic->publish( m );
// remember to use
// ros::spinOnce();

open a subscriber

// define globally 
ros::Subscriber *sub_top;
pkg__msg last_msg; // a copy of the previously received message

// define the "callback"
void cbk_top( const pkg::msg::ConstPtr& pm )
{
	// save the last message
	last_msg = pm;
	
	// ...
}

// in main() define the interface
TLOG( "subscribing to the topic " << LOGSQUARE( TOPIC_TOP ) << "..." );
ros::Subscriber tsub_top = nh.subscribe( TOPIC_TOP, Q_SZ_TOP, cbk_top );
sub_top = &tsub_top;
TLOG( "subscribing to the topic " << LOGSQUARE( TOPIC_TOP ) << "... OK" );

Open a subscriber with the callback in a class

☝️ The method must be public.

let's say the class my_class contains the methods cbk_top() you want to use as callback. The syntax changes a bit:

TLOG( "subscribing to the topic " << LOGSQUARE( TOPIC_TOP ) << "..." );
ros::Subscriber tsub_top = nh.subscribe( TOPIC_TOP, Q_SZ_TOP, &my_class::cbk_top, &my_class );
sub_top = &tsub_top;
TLOG( "subscribing to the topic " << LOGSQUARE( TOPIC_TOP ) << "... OK" );

The function prototype is the same.

🎰 ROS1 C++ services

Global definitions

// import the message type (pkg::srv , pkg::srv::Request , pkg::srv::Response)
#include <pkg::srv.h>

// global definitions
#define SERVICE_SERV "/serv"

open a service

// define globally a pointer to the service
ros::ServiceServer *srv_serv;
pkg::srv last_srv;

// prototype of the callback
bool cbk_serv( pkg::srv::Request& req, pkg::srv::Response& res )
{
	// save the request
	last_srv.request = req;
	
	// elaborate the response
	// res.field = value;
	
	// ...
	
	// save the response
	last_srv.response = res;
	
	return true;
}

// within the main function
OUTLOG( "Advertising service " << LOGSQUARE( SERVICE_SERV  ) << "..." );
ros::ServiceServer tsrv_serv = nh.advertiseService( SERVICE_SERV, cbk_serv );
srv_serv = &tsrv_serv;
OUTLOG( "Advertising service " << LOGSQUARE( SERVICE_SERV ) << "... OK" );

Open up a service using a class method as callback

☝️ The method must be public.

not so different. my_class containts the method cbk_serv I want to use as callback. Instanciate the class, then open the service:

// within the main function
OUTLOG( "Advertising service " << LOGSQUARE( SERVICE_SERV  ) << "..." );
ros::ServiceServer tsrv_serv = nh.advertiseService( SERVICE_SERV, &my_class::cbk_serv, &my_class );
srv_serv = &tsrv_serv;
OUTLOG( "Advertising service " << LOGSQUARE( SERVICE_SERV ) << "... OK" );

open up a client

// define also this
#define TIMEOUT_SERV 5 //seconds

// client definition
ros::ServiceClient *cl_serv;

// main function
TLOG( "Opening client " << LOGSQUARE( SERVICE_SERV ) << "..." );
ros::ServiceClient tcl_serv = nh.serviceClient<pkg::srv>( SERVICE_SERV );
if( !tcl_serv.waitForExistence( ros::Duration( TIMEOUT_SERV ) ) )
{
	TERR( "unable to contact the server - timeout expired (" << TIMEOUT_SERV << "s) " );
	return 0;
}
cl_serv = &tcl_serv;
TLOG( "Opening client " << LOGSQUARE( SERVICE_SERV ) << "... OK" );

Use a client

ROS1 clients are always blocking, unless you don't create a thread in charge of managing the request.

// service message (we're going to fill in the request only)
pkg::srv sm;

// populate the request
// sm.request.field = value;

// call the service (blocking)
if( !cl_serv->call( sm ) ) 
{ 
	TERR( "unable to make a service request -- failed calling service " 
		<< LOGSQUARE( SERVICE_SERV ) 
		<< (!cl.exists( ) ? " -- it seems not opened" : "") );
	return 0;
}

// use the response
// sm.response.field

client using bare callings

🔗 see ros::service Namespace reference

This style allows to not create a handle for dealing with the service. Here are two ways to check if a service exists:

// === FIRST METHOD -- NON BLOCKING === //

// check if a service exists (non-blocking)
ros::service::exists( SERVICE_SERV )
{
    // the service exists... do whatever you want inside here
}

// === SECOND METHOD -- BLOCKING === //

// wait for the existence of a service (blocking, at least for a given time)
if( !ros::service::waitForExistence( SERVICE_SERV, ros::Duration( TIMEOUT_SERV ) ) )
{
	TERR( "unable to contact the server - timeout expired (" << TIMEOUT_SERV << "s) " );
	return 0;
}

And here is how to call the service using bare calling style:

// service message (we're going to fill in the request only)
pkg::srv sm;

// populate the request
// sm.request.field = value;

// call the service (blocking)
if( !ros::service::call( SERVICE_SERV, sm ) ) 
{ 
	TERR( "unable to make a service request -- failed calling service " 
		<< LOGSQUARE( SERVICE_SERV ) 
		<< (!ros::service::exists( SERVICE_SERV )) ? " -- it seems not opened" : "") );
	return 0;
}

// use the response
// sm.response.field

⚙️ ROS1 C++ Actions

Open a Action Service

open a Action Client

💾 ROS1 C++ Parameter Server

🔗 see the official ROS Wiki

🔗 in particular, see the C++ interface for ROS parameter server

SET -- Load data in the parameter server

ros::param::set("/global_param", 5);
ros::param::set("relative_param", "my_string");
ros::param::set("bool_param", false);

Load data from Bash

🔗 see the ROSparam command line

# set one value
rosparam set parameter_name value

# from file
rosparam load param-file.yaml

Loading from YAML files the parameters is for sure the most interesting method. Here's a simple example of YAML parameters files, just to appreciate a bit of its syntax:

string: 'foo'
integer: 1234
float: 1234.5
boolean: true
list: [1.0, mixed list]
dictionary: {a: b, c: d}

Load data from Launch Files

<!-- one parameter -->
<param name="somefloat1" value="3.14159" type="double" />

<!-- a YAML file -->
<param name="configfile" textfile="$(find roslaunch)/example.yaml" />

DEL -- Delete data in the parameter server

ros::param::del("my_param");

CHECK -- check if data exist

if( ros::param::has("my_param") )
{
    // it exists... yuppie!
}

GET -- read data from the parameter server

ros::param::get("/global_name", value_reference);
Clone this wiki locally