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:
- Set up package
- Custom msg file
- talker.cpp
- listener.cpp
- 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.
Node | Relative (basic) | Global | Private |
---|---|---|---|
/node1 | bar->/bar | /bar->/bar | ~bar->/node1/bar |
/wg/node2 | bar->/wg/bar | /bar->/bar | ~bar->/wg/node2/bar |
/wg/node3 | foo/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; }