-
Notifications
You must be signed in to change notification settings - Fork 0
ROS1 Cpp Interfaces
- I'm referring especially to the ROS1 simple node pattern, but if you need a class patter for the node, few updates are needed.
- 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
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 1000Some 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 (asstd::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" );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();// define globally
ros::Subscriber *sub_top;
pkg::msg last_msg_top; // a copy of the previously received message
// define the "callback"
void cbk_top( const pkg::msg::ConstPtr& pm )
{
// save the last message
last_msg_top = 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" );βοΈ 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.
// import the message type (pkg::srv , pkg::srv::Request , pkg::srv::Response)
#include <pkg::srv.h>
// global definitions
#define SERVICE_SERV "/serv"// 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" );βοΈ 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" );// 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" );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π 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βοΈ here we're using simpleActionClient, meaning that a node can implement as many actions as it wants, but running only one instance per action. see this post for further information.
for Action Service:
#include <actionlib/server/simple_action_server.h>
#include <string>
// include the message 'act' from inside the package 'pkg'
#include <pkg/actAction.h>
// it defines 4 types of messages
// pkg::actAction
// pkg::actGoal
// pkg::actResult
// pkg::actFeedback
// plus...
// pkg::act
// define for the action service
#define ACTION_ACT "act"
#define RATE_ACT 5for Action Client:
#include "actionlib/client/simple_action_client.h"
#include "actionlib/client/terminal_state.h"
#include <string>
// include the message 'act' from inside the package 'pkg'
#include <pkg/actAction.h>
// it defines 4 types of messages
// pkg::actAction
// pkg::actGoal
// pkg::actResult
// pkg::actFeedback
// plus...
// pkg::act
// define for the action service
#define ACTION_ACT "act"
#define TIMEOUT_ACT 5
#define TIMEOUT_WAIT_ACT 0.1
#define RES_ACT (actcl_act->getResult( ))π more info inside the official Wiki
π the implementation includes also the time spent in working on the goal. See time in ROS to know more about it.
π if you want to manage multiple requests in parallel on the same action server, see the actionlib class reference
Here's a customizable implementation of a action server. act is the name of the implemented action. Remember to adapt the names to your application!
class actclass_act
{
protected:
// the nodeHandle
ros::NodeHandle nh;
// the action service handle
actionlib::SimpleActionServer<pkg::act> as;
// the service name
std::string action_name;
// last sent feedback
pkg::actFeedback feedback;
// last sent result
pkg::actResult result;
// starting time for the action
ros::Time t_start = ros::Time::now( );
// action end time
ros::Time t_end = ros::Time::now( );
// time spent for the previously completed action
ros::Duration t( 0 );
public:
// action class constructor
actclass_act( std::string aname = ACTION_ACT ) :
as( nh, aname, boost::bind(&actclass_act::cbk_act, this, _1), false ),
action_name( aname )
{
// ...
as.start( );
}
// destructor
~actclass_act( void )
{
// ...
}
// TODO action implementation
void cbk_act( const pkg::act::ConstPtr& goal )
{
TLOG( "action " << LOGSQUARE( ACTION_ACT ) << " START" );
// this->print_goal( goal );
bool success = false;
ros::Rate r( RATE_ACT );
// TODO to set a known initial state before executing the main loop
// this->reset_class( );
this->t_start = ros::Time::now( );
while( true )
{
// check first for preemption
if ( as.isPreemptRequested( ) || !ros::ok( ) )
{
TLOG( "[" << LOGSQUARE( ACTION_ACT ) << "] CANCELLED " );
as.setPreempted( );
success = false;
break;
}
// TODO working on the message ...
// ...
// TODO fill in the feedback
// this->feedback.field = value;
as.publishFeedback( this->feedback );
// sleep until the next cycle
r.sleep( );
}
this->t_end = ros::Time::now( );
this->t = this->t_end - this->t_start;
if( success )
{
TLOG( "[" << LOGSQUARE( ACTION_ACT ) << "] SUCCESS "
<< "(in " << this->t.toSec( ) << "s)" );
// TODO fill in the result
// this->result.field = value;
as.setSucceed( this->result );
}
else
{
// TODO what to do in case of failure?
// TLOG( "[" << LOGSQUARE( ACTION_ACT ) << "] FAILED " << "(in " << this->t.toSec( ) << "s)" );
}
}
private:
// TODO other class data ...
// ...
// TODO print the message
void print_goal( const pkg::act::ConstPtr& goal )
{
/*
TLOG( "[" << LOGSQUARE( ACTION_ACT ) << "] Received goal: \n"
<< "\t" << goal.field << " \n"
);
*/
}
// TODO print the result
void print_result( const pkg::actResult& res )
{
/*
TLOG( "[" << LOGSQUARE( ACTION_ACT ) << "] Sending result: \n"
<< "\t" << res.field << " \n"
);
*/
}
// TODO set a known state before starting the loop
void reset_class( )
{
// empty the class messages
this->feedback = pkg::actFeedback( );
this->result = pkg::actResult( );
// reset the timer
this->t_start = ros::Time::now( );
this->t_end = ros::Time::now( );
this->t = ros::Duration( 0 );
// TODO other operations before starting
// ...
}
}// define globally
actclass_act *act_act;
// then, in main function ...
TLOG( "opening action " << LOGSQUARE( ACTION_ACT ) << " ... " );
actclass_act tact_act( ACTION_ACT );
act_act = &tact_act;
TLOG( "opening action " << LOGSQUARE( ACTION_ACT ) << " ... OK!" );π here's a good example of client implementation even if a bit limited.
π see also the actionlib::SimpleActionClient class reference
π see this reference to understand how to deal with service states. See also getState()
π see this post, which discusses a class implementation of the Action Client. Interesting also for showing another way to pass the goal to the service.
// define globally
actionlib::SimpleActionClient<pkg::act> *actcl_act( );
// in the main function ...
TLOG( "opening action client " << LOGSQUARE( ACTION_ACT ) << " ... " );
actionlib::SimpleActionClient<pkg::act> tactcl_act( ACTION_ACT, true );
if( !tactcl_act.waitForServer( ros::Duration( TIMEOUT_ACT ) ) )
{
TERR( "unable to connect to the action server (timeout " << TIMEOUT_ACT << "s) "
<< "-- action " << LOGSQUARE( ACTION_ACT ) << "\n"
<< "\t " << (tactcl_act.isServerConnected( ) ? " it seems not online " : " service online ") ) << "\n"
<< "\t" << "STATUS: " << tactcl_act.getState( ).toString( ) );
return 0;
}
actcl_act = & tactcl_act;
TLOG( "opening action client" << LOGSQUARE( ACTION_ACT ) << " ... OK!" );First phase: create the goal and send it to the service.
// declare the goal
pkg::actGoal goal;
// fill in the goal
// goal.field = value;
// send the goal
actcl_act->sendGoal( goal );Simplified waiting, blocking:
waitForResults( ) is completely blocking: it makes the entire process to sleep!
βοΈ Look at this page about asyncSpin(). Using this kind of spinner allows to use this kind of waiting without blocking other interfaces implemented in the same node, if any.
π see also the ros::CallbackQueueInterface Class
if ( !actcl_act->waitForResult( ros::Duration(TIMEOUT_ACT) ) )
{
TERR( "action client for " << LOGSQUARE( ACTION_ACT ) << "TIMEOUT EXPIRED " );
actcl_act->cancelAllGoals( );
// TODO how to deal with it?
// ...
}
// obtain the pointer to the result
auto res = actcl_act->getResult( );
// res->field; CONST PTR!Manual waiting, non-blocking and appliable in a single-thread context (but choosing a small timeout):
{
float wtime = 0.0;
std::string state = "ACTIVE";
while( true )
{
// wait and spin
(ros::Duration( TIMEOUT_WAIT_ACT )).sleep( );
ros::spinOnce( );
// TODO other things here ... ?
// ...
// check the status
wtime += TIMEOUT_WAIT_ACT;
state = actcl_act.getState( ).toString( );
if( state == "SUCCEEDED" )
{
TLOG( LOGSQUARE( ACTION_ACT ) << " SUCCESS in " << (int) wtime << "s" );
// ...
break;
}
else if( state == "PREEMPTED" )
{
TLOG( LOGSQUARE( ACTION_ACT ) << " CANCELLED!" );
break;
}
else if( wtime > TIMEOUT_ACT )
{
TLOG( LOGSQUARE( ACTION_ACT ) << "unable to complete the action in time; cancelling ..." );
actcl_act->cancelAllGoals( );
break;
}
}
}
// obtain the pointer to the result
auto res = actcl_act->getResult( );
// res->field; CONST PTR!very simple: just use
actcl_act->cancelAllGoals( );π see the official ROS Wiki
π in particular, see the C++ interface for ROS parameter server
ros::param::set("/global_param", 5);
ros::param::set("relative_param", "my_string");
ros::param::set("bool_param", false);π see the ROSparam command line
# set one value
rosparam set parameter_name value
# from file
rosparam load param-file.yamlLoading 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}<!-- one parameter -->
<param name="somefloat1" value="3.14159" type="double" />
<!-- a YAML file -->
<param name="configfile" textfile="$(find roslaunch)/example.yaml" />ros::param::del("my_param");if( ros::param::has("my_param") )
{
// it exists... yuppie!
}ros::param::get("/global_name", value_reference);