ros Learning Record 5 Client Library CPP

5.Client Library CPP

client library:

Provide library/interface for ros programming

For example, set up a node, publish a message, invoke a service, and so on

Provides support for both cpp and python languages, typically using cpp

  • roscpp

  • rospy

5.1 roscpp

ros provides an interface for using cpp and topic,service,param, and so on

ROS CPP is located under/opt/ros/kinetic s and uses CPP to achieve ROS communication. In ROS, the code for CPP is compiled and built using the catkin compilation system (extended CMake). So simply understand that you can also think of roscpp as a library of cpp. We create a CMake project in which libraries of ROS such as roscpp are included so that the functions provided by ROS can be used in the project. Usually we call the CPP interface of ROS, first we need #include <ros/ros.h>.

The main components of roscpp include:

  • ros::init(): Resolve the incoming ROS parameter and create the function needed for the first step of the node

  • ros::NodeHandle: A common interface for interacting with topic s, service s, param s, and so on

  • ros::master:Function containing query information from master

  • ros::this_node: Contains functions to query this process (node)

  • ros::service: A function that contains a query service

  • ros::param: A function that contains a query parameter server without using NodeHandle

  • ros::names: Contains functions for handling ROS diagram resource names

  • Specifically visible: http://docs.ros.org/api/roscpp/html/index.html

5.1.1 ros::init()

void ros::init()   //Resolve the ros parameter, named for this node

5.1.2 ros::NodeHandle Class

// Create a publisher for the topic
ros::Publisher advertise(const string &topic, uint32_t queue_size, bool latch=false);
//The first parameter is the name of the published topic
//The second is the maximum length of the message queue, and if the published message exceeds this length and is not received, the message will be queued. Usually set to one
 A smaller number will do.
//The third parameter is whether to latch or not. Some topic s aren't published at a certain frequency, such as / map, but only on initial subscriptions or map updates
 In both cases, /map Will publish the message. Locks are used here.
//Create a subscriber for the topic
ros::Subscriber subscribe(const string &topic, uint32_t queue_size, void(*)(M));
//The first parameter is the name of the subscription topic
//The second parameter is the length of the subscription queue. If the message received is not processed in time, the new message will be queued and the message will be queued.
//The third parameter is the callback function pointer, which points to the callback function to process the received message
//Create a server for services, provide services
ros::ServiceServer advertiseService(const string &service, bool(*srv_func)(Mreq &, Mre
s &));
//The first parameter is the service name
//The second parameter is a pointer to the service function, which points to the service function. The function you point to should have two parameters that accept requests and responses, respectively.
//Create a client for the service
ros::ServiceClient serviceClient(const string &service_name, bool persistent=false);
//First Functional service Name
//The second parameter sets whether the connection to the service will continue, and if true, the client will remain connected to the remote host so that subsequent requests will occur
 Faster. Usually we set the flase
//Query the value of a parameter
bool getParam(const string &key, std::string &s);
bool getParam (const std::string &key, double &d) const;
bool getParam (const std::string &key, int &i) const;
//Getting the value for the key from the parameter server has overloaded several types
//Assigning values to parameters
void setParam (const std::string &key, const std::string &s) const;
void setParam (const std::string &key, const char *s) const;
void setParam (const std::string &key, int i) const;
//Assigning a value to the key overloads multiple types of val

5.1.3 ros::spin() and ros::spinOnce() Differences and Details

1 Functional Meaning

First of all, these two brothers are named ROS message callback handlers. They usually occur in the main loop of ROS, and the program needs to call ros::spin() or ros::spinOnce() continuously. The difference is that the former does not return after calling, that is, your main program does not go down here, and the latter can continue after calling.

In fact, the principle of the message callback handler is very simple. We all know that ROS has a message publishing subscription mechanism, what? I do not know! Don't know if you're going soon: http://wiki.ros.org/ROS/Tutorials (ROS Official Basic Tutorial) Take a look.

Okay, let's continue. If your program writes a message subscription function, then in addition to the main program, ROS automatically accepts subscribed messages in the background in the format you specify, but the messages it receives are not processed immediately, but must wait until ros::spin() or ros::spinOnce() Called only when executed, which is how the message callback function works

2 Differences

As mentioned above, ros::spin() will not return after the call, that is, your main program will not execute down here, while ros::spinOnce() which can continue to execute after the call.

Actually, the name of a function can be understood almost as well, one is called all the time. Another is to call it only once, and if you want to call it again, you need to add a loop.

It is important to remember here that the ros::spin() function does not normally appear in a loop, because the program does not call other statements after spin(), which means that the loop is meaningless, and that there must be no other statements (except return 0) after the spin() function, which are either free or not executed. ros::spinOnce() is relatively flexible, but it is often necessary to consider the timing, frequency, and size of the message pool when the message is invoked, which should be coordinated with reality or cause data packet loss or latency errors

3 Common usage methods

It's important to emphasize here that if your brother or your program writes a message subscription function, don't forget to add the ros::spin() or ros::spinOnce()** function in the corresponding location**, otherwise you will never get data or messages from the other side, blogger's blood lesson, keep in mind

3.1 ros::spin()

The ros::spin() function is relatively simple to use, usually at the end of the main program, by adding the statement. Examples are as follows:

Sender:

#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
 
int main(int argc, char **argv)
{
    ros::init(argc, argv, "talker");
    ros::NodeHandle n;
    ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
    ros::Rate loop_rate(10);
 
    int count = 0;
    while (ros::ok())
    {
        std_msgs::String msg;
        std::stringstream ss;
        ss << "hello world " << count;
        msg.data = ss.str();
        ROS_INFO("%s", msg.data.c_str());
 
        /**
         * Send a message to Topic: chatter at 10 Hz (10 times per second); Message pool maximum capacity 1000.
         */
        chatter_pub.publish(msg);
 
        loop_rate.sleep();
        ++count;
    }
    return 0;
}

receiving end

#include "ros/ros.h"
#include "std_msgs/String.h"
 
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
    ROS_INFO("I heard: [%s]", msg->data.c_str());
}
 
int main(int argc, char **argv)
{
    ros::init(argc, argv, "listener");
    ros::NodeHandle n;
    ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
 
    /**
     * ros::spin() The callback function chatterCallback() is called all the time, calling 1000 data at a time.
     * Exit when the user enters Ctrl+C or the ROS main process closes.
     */
    ros::spin();
    return 0;
}

3.2 ros::spinOnce()

The use of ros::spinOnce() is freer than ros::spin() and can occur in all parts of the program, but there are more factors to be aware of. For example:

1 For some messages that transmit very fast, particular attention should be paid to reasonable control of message pool size and ros::spinOnce() execution frequency; For example, if the message delivery frequency is 10Hz and the invocation frequency of ros::spinOnce() is 5Hz, then the size of the message pool must be greater than 2 to ensure no data loss and no delay.

/**Receiver**/
#include "ros/ros.h"
#include "std_msgs/String.h"
 
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
   /*...TODO...*/ 
}
 
int main(int argc, char **argv)
{
   ros::init(argc, argv, "listener");
   ros::NodeHandle n;
   ros::Subscriber sub = n.subscribe("chatter", 2, chatterCallback);
 
   ros::Rate loop_rate(5);
   while (ros::ok())
   {
       /*...TODO...*/ 

       ros::spinOnce();
       loop_rate.sleep();
   }
   return 0;

2. ros::spinOnce() is used flexibly and extensively, requiring specific analysis. However, for user-defined periodic functions, it is best to execute side-by-side with ros::spinOnce, rather than in callback functions.

/*...TODO...*/
ros::Rate loop_rate(100);
 
while (ros::ok())
{
   /*...TODO...*/
   user_handle_events_timeout(...);

   ros::spinOnce();                 
   loop_rate.sleep();
}

5.2 topic_demo

Topic is a model of asynchronous communication in ROS. Generally, there is a clear division of work between nodes, some only responsible for sending, some only responsible for receiving and processing. Topic model is the most appropriate communication mode for most robot scenarios, such as sensor data sending and receiving, and speed control command sending and receiving.

An example of sending and receiving messages: customizing a message of type gps (including location x, y, and status information), a node
Simulated gps messages are published at a certain frequency, and another node receives and processes them to calculate the distance to the origin.

Steps:

  1. Set up package
  2. Custom msg file
  3. talker.cpp
  4. listener.cpp
  5. CmakeList.txt & package.xml

1. Create a package

cd ~/tutorial_ws/src
catkin_creat_pkg topic_demo roscpp rospy std_msgs

2. Create a new custom msg

cd topic_demo/
mkdir msg
cd msg
gedit gps.msg


catkin_make will compile the *.msg file into a *.h file, and #include will work in one go

#include "topic_demo/gps.h"
topic_demo::gps msg;

3. talk.cpp

  • Writing of.pro files, mainly h files generated by ros and msg
# topic_demo.pro
TEMPLATE = app
CONFIG += console cpp11
CONFIG -= app_bundle
CONFIG -= qt

SOURCES += \
    talker.cpp

LIBS += \
    -L/usr/local/lib \
    -L/opt/ros/kinetic/lib \
    -lroscpp -lrospack -lpthread -lrosconsole -lrosconsole_log4cxx\
    -lrosconsole_backend_interface -lxmlrpcpp -lroscpp_serialization -lrostime  -lcpp_common  -lroslib -lroslib


HEADERS += \


INCLUDEPATH +=\
/opt/ros/kinetic/include\
/home/swc/tutorial_ws/devel/include/     # This is / devel/include / of the ros workspace
/*talker.cpp*/
#include <iostream>
#include "ros/ros.h"
#include "topic_demo/gps.h"

using namespace std;

int main(int argc,char** argv)
{
    ros::init(argc,argv,"talker");  //Initialize, parse parameters, named node as "talker"
    ros::NodeHandle nh;             //Create handle, instantiate node
    topic_demo::gps msg;            //Create a gps message
    msg.x = 1.0;                    //msg initialization
    msg.y = 1.0;
    msg.state = "working";
    ros::Publisher pub = nh.advertise<topic_demo::gps>("gps_info",1);//Create publisher
    //Ros:: Publisher pub = nh.advertise<message format> ("topic name", queue_size)
    //queue_size indicates the length of the cache queue, send and receive at any time, set to 1 so that a smaller number is possible
    ros::Rate loop_rate(1.0);//ros::Rate is a class that controls loops and defines how often loops are published, here at 1Hz
    while(ros::ok())         //ros::ok() means that as long as ros is not turned off, it will continue to cycle
    {
        //Simulate changes in data
        msg.x = 1.03 * msg.x;//Exponentially growing every 1 second
        msg.y = 1.01 * msg.y;
        //Output current msg,ROS_INFO and printf(),cout are similar
        ROS_INFO("Talker_____:GPS:x = %f,y = %f",msg.x,msg.y); 
        pub.publish(msg);       //Publish a message
        loop_rate.sleep();      //sleep for one second based on defined publishing frequency
    }
    return 0;
}

4.listener.cpp

/*listener.cpp*/
#include "ros/ros.h"
#include "topic_demo/gps.h"

//topic_demo::gps::ConstPtr is also a class
//It is a constant pointer to topic_demo::gps
//callback
void gpsCallback(const topic_demo::gps::ConstPtr& msg)
{
    float distance;
    float x = msg->x;
    float y = msg->y;
    distance = sqrt(pow(x,2)+pow(y,2));
    ROS_INFO("Listener_____distance to ogigin:%f,state:%s",distance,msg->state);
}

int main(int argc, char **argv)
{
    ros::init(argc,argv,"listener");
    ros::NodeHandle nh;
    /*
    Create subscribe
    ros::Subscriber sub = nh.subscribe("Listening topic name ", message queue length, callback function (pointer);
    "Listening topic name ": set to the same as publisher
    Message queue length, general messages will be processed as soon as they arrive, so don't set it too large unless you want to cache the data and delay processing
    Callback function: handles messages received from listening topic
    */
    ros::Subscriber sub = nh.subscribe("gps_info",1,gpsCallback);
    /*
    In fact, if you don't get a message, put it in the queue. Instead of one message automatically processing one, you call a spin() function
    spin()Function, to see if there are any messages in the current queue, if there are, call the callback function to process, empty the queue, if the queue is empty, will not process
    Repeatedly calling the current triggerable callback function is a blocked function, to execute this sentence, repeatedly check to see if there are any callback functions that can be executed
    There is also a ros::spinOnce() function that checks only once for callback functions that can be executed, and if not, executes down
    */
    ros::spin();
    return 0;
}

5. Modify CMakeLists.txt and package.xml

CMakeLists.txt

cmake_minimum_required(VERSION 2.8.3)   # CMAKE Version
project(topic_demo)		        # entry name

find_package(catkin REQUIRED COMPONENTS	# Specify Dependencies
  roscpp
  rospy
  std_msgs
message_generation
)
				
add_message_files(			# Add custom msg
   FILES
gps.msg
)

generate_messages(DEPENDENCIES std_msgs)# Generate h file corresponding to msg

catkin_package(CATKIN_DEPENDS roscpp rospy std_msgs message_runtime)# Used to configure ROS and package configuration files and Cmake files

include_directories(include ${catkin_INCLUDE_DIRS})    # Specify header file path for C/cpp

add_executable(talker src/talker.cpp)			# Generate executable target file
add_dependencies(talker topic_demo_generate_message_cpp)# To add a dependency, do you have to have this sentence to generate an msg?
target_link_libraries(talker ${catkin_LIBRARIES})		# link

add_executable(listener src/listener.cpp)
add_dependencies(listener topic_demo_generate_message_cpp)
target_link_libraries(listener ${catkin_LIBRARIES})

Add two sentences to package.xml

<build_depend>message_generation</build_depend>
<run_depend>message_runtime</run_depend>

6. Compile and run

catkin_make

rosrun topic_demo talker

rosrun topic_demo listener

You can also compile and run listener.cpp in qt with the same effect

View relationships between topics: rqt_graph


7. Receive messages before processing them

/*listener.cpp*/
#include "ros/ros.h"
#include "topic_demo/gps.h"

topic_demo::gps gps_data;//Dump Received Data

void gpsCallback(const topic_demo::gps::ConstPtr& msg)
{
    gps_data = *msg;
    gps_data.state = "received";
    float distance;
    float x = msg->x;
    float y = msg->y;
    distance = sqrt(pow(x,2)+pow(y,2));
    //ROS_INFO("Listener_____distance to ogigin:%f,state:%s",distance,msg->state.c_str());
}

int main(int argc, char **argv)
{
    ros::init(argc,argv,"listener");
    ros::NodeHandle nh;
    ros::Publisher pub = nh.advertise<topic_demo::gps>("gps_receive",1);
    ros::Subscriber sub = nh.subscribe("gps_info",1,gpsCallback);
    ros::Rate loop_rate(1.0);
    while(ros::ok())
    {
        ros::spinOnce();//Set to spinOnce(), or you won't be able to run out
        pub.publish(gps_data);//You can also pub in a callback function
	ROS_INFO("publish  success");
        loop_rate.sleep();
    }
    return 0;
}
/*listener2.cpp*/
#include "ros/ros.h"
#include "topic_demo/gps.h"

void gpsCallback(const topic_demo::gps::ConstPtr& msg)
{
    float distance;
    float x = msg->x;
    float y = msg->y;
    distance = sqrt(pow(x,2)+pow(y,2));
    ROS_INFO("Listener2_____distance to ogigin:%f,state:%s",distance,msg->state.c_str());
}

int main(int argc, char **argv)
{
    ros::init(argc,argv,"listener2");
    ros::NodeHandle nh;
    ros::Subscriber sub = nh.subscribe("gps_receive",1,gpsCallback);
    ros::Rate loop_rate(1.0);
    while(ros::ok())
    {
        ros::spin();
	loop_rate.sleep();
    }
    return 0;
}

8. Topic names are relevant

The code below shows the declaration of a common topic, and we understand its use by modifying the topic name.

int main(int argc, char** argv) 		 // Node Principal Function
{
    ros::init( argc, argv, "node1"); 		 // Initialize Node
	{
    ros::NodeHandle nh; 				
    // Declare publisher, topic name = bar
    // Declare node handle
    ros::Publisher node1_pub = nh.advertise<std_msg::Int32>("bar" , 10);
	}
}

The node name here is/node1. If you declare a publisher with a bar in a relative form without any characters, the topic will have the same name as/bar.

If the slash (/) character is used as a global form as shown below, the topic name is also/bar.

ros::Publisher node1_pub = nh.advertise<std_msg::Int32>("/bar", 10);
However, if you use the tilde (~) character to declare it private, the topic name will change to/node1/bar.

ros::Publisher node1_pub = nh.advertise<std_msg::Int32>("~bar", 10);

This can be used in various ways as shown in the table below. Where/wg means a modification of the namespace. This is discussed in more detail in the description below.

NodeRelative (basic)GlobalPrivate
/node1bar->/bar/bar->/bar~bar->/node1/bar
/wg/node2bar->/wg/bar/bar->/bar~bar->/wg/node2/bar
/wg/node3foo/bar->/wg/foo/bar/foo/bar->/foo/ba~foo/bar->/wg/node3/foo/bar

5.3 service_demo

services is another way of ros communication, specifically 3.2.4 3.2.5

demo function:

Adds two integers

Client: Send two integers

Server: Return summation

1. Custom srv file

Similar to topic's msg, create a new srv folder in the feature pack that holds custom srv files

# Client Sent
int64 a
int64 b
# Client and Server Use - Separate
---
# Server returned
int64 sum

2. Generate formats that cpp can call

Modify Cmakelists.txt package.xml

Add feature package dependencies to package.xml:

<?xml version="1.0"?>
<package format="2">
  <name>service_demo</name>
  <version>0.0.0</version>
  <description>service_demo</description>
  <maintainer email="swc@todo.todo">swc</maintainer>

  <license>TODO</license>
    
  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>roscpp</build_depend>
  <build_depend>rospy</build_depend>
  <build_depend>std_msgs</build_depend>
  <build_depend>message_generation</build_depend>

  <build_export_depend>roscpp</build_export_depend>
  <build_export_depend>rospy</build_export_depend>
  <build_export_depend>std_msgs</build_export_depend>

  <exec_depend>roscpp</exec_depend>
  <exec_depend>rospy</exec_depend>
  <exec_depend>std_msgs</exec_depend>
  <exec_depend>message_runtime</exec_depend>  

  <!-- The export tag contains other, unspecified, tags -->
  <export>
    <!-- Other tools can request additional information be placed here -->

  </export>
</package>

Add Dependency Options in Cmakelists.txt

cmake_minimum_required(VERSION 3.0.2)
project(service_demo)

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)

################################################
## Declare ROS messages, services and actions ##
################################################

## Generate services in the 'srv' folder
add_service_files(
  FILES
  AddTwoInts.srv
)


## Generate added messages and services with any dependencies listed here
generate_messages(
  DEPENDENCIES
  std_msgs
)


###################################
## catkin specific configuration ##
###################################
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES service_demo
 CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)

###########
## Build ##
###########

## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
include
  ${catkin_INCLUDE_DIRS}
)

Then compile to generate the h file

3. Client Programming

/**
 * AddTwoInts Client
 */
 
#include <cstdlib>
#include "ros/ros.h"
#include "learning_communication/AddTwoInts.h"

int main(int argc, char **argv)
{
  // ROS Node Initialization
  ros::init(argc, argv, "add_two_ints_client");
  
  // Get two additions from the terminal command line
  if (argc != 3)
  {
    ROS_INFO("usage: add_two_ints_client X Y");
    return 1;
  }

  // Create node handle
  ros::NodeHandle n;
  
  // Create a client and request add_two_int service, service message type is learning_communication::AddTwoInts
  // Service name provided by server sent to: add_two_ints
  ros::ServiceClient client = n.serviceClient<learning_communication::AddTwoInts>("add_two_ints");
  
  // Create learning_communication::AddTwoInts type service message
  learning_communication::AddTwoInts srv;
  srv.request.a = atoll(argv[1]);
  srv.request.b = atoll(argv[2]);
  
  // Publish a service request, wait for the result of the addition operation, call the service, return true if the call succeeds, and the value in srv.response is valid, and vice versa
  if (client.call(srv))
  {
    ROS_INFO("Sum: %ld", (long int)srv.response.sum);
  }
  else
  {
    ROS_ERROR("Failed to call service add_two_ints");
    return 1;
  }

  return 0;
}

4. Server-side Programming

/**
 * AddTwoInts Server
 */
 
#include "ros/ros.h"
#include "learning_communication/AddTwoInts.h"

// service callback function, input parameter req, output parameter res
// It seems that the parameters of a callback function can only be written as follows: req; res, try to pass only AddTwoInts error
bool add(learning_communication::AddTwoInts::Request  &req,
         learning_communication::AddTwoInts::Response &res)
{
  // Add the request data from the input parameters and place the results in the response variable
  res.sum = req.a + req.b;
  ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
  ROS_INFO("sending back response: [%ld]", (long int)res.sum);
  
  return true;
}

int main(int argc, char **argv)
{
  // ROS Node Initialization
  ros::init(argc, argv, "add_two_ints_server");
  
  // Create node handle
  ros::NodeHandle n;

  // Create a name called add_two_ints server, broadcast it to ros
  // Register callback function add()
  ros::ServiceServer service = n.advertiseService("add_two_ints", add);
  
  // Loop Wait Callback Function
  ROS_INFO("Ready to add two ints.");
  ros::spin();

  return 0;
}

Keywords: C++ git ROS 3d

Added by knighthawk1337 on Sun, 21 Nov 2021 21:12:27 +0200