Wiki
Big Four telecom
Existing English translations were reused. 18 current source blocks remain in Simplified Chinese, without triggering paid translation.
1.Introduction to Communication Mechanisms and Code Templates

#include "rclcpp/rclcpp.hpp"
class MyNode: public rclcpp::Node
{
public:
MyNode():Node("mynode_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"Hello World!");
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<MyNode>());
rclcpp::shutdown();
return 0;
}


https://snippet-generator.app/



{
"ROS2节点模板(C++)": {
"prefix": "ros2_node_cpp",
"body": [
"#include \"rclcpp/rclcpp.hpp\"",
"",
"class MyNode: public rclcpp::Node",
"{",
" public:",
" MyNode():Node(\"mynode_node_cpp\")",
" {",
" RCLCPP_INFO(this->get_logger(),\"Hello World!\");",
" }",
"};",
"",
"int main(int argc, char ** argv)",
"{",
" rclcpp::init(argc,argv);",
"",
" rclcpp::spin(std::make_shared<MyNode>());",
"",
" rclcpp::shutdown();",
" return 0;",
"}"
],
"description": "ROS2节点模板(C++)"
}
}





The first window is the server side, and the second window is the client side.


Communication involves at least two parties; a single person alone does not constitute communication.



Face the interface; if the topic is consistent and the data carrier is also consistent, seamless integration can be achieved.


Topic communication: Data can only be transmitted in one direction
Service communication: bidirectional communication, where each party can act as both client and server; the client sends data to the server, and the server responds to the client.
Action communication: it is very similar to service communication, in that there is a final response sent from the server to the client, but feedback is also sent continuously to the client in the meantime.
Parameter service: The parameter service is based onservice communication. The parameter client first sends a request and then takes data from the parameter service's data pool. It can also modify the contents of the data pool, but cannot delete them.







For parameter communication, you do not need to define the interface file yourself; the system will create the interface file automatically. However, developers cannot see this file because it is encapsulated.
The data we operate on has been encapsulated into a parameter object.

ros2 pkg create + package name (can be written before or) + --build-type (build type) + ament_cmake / ament_python + --dependencies (dependencies) + rclcpp (ROS2 CPP client) + --node-name (node name) + node name

Create a functional package in src

2.话题通信_理论




In ROS1, communication between nodes must go through the master. Each node that transmits data needs to register the relevant data with the master, and the master then matches the information.


When one publisher publishes data, both subscribers will receive the data.



3.话题通信_实验1(C++)






ros2 pkg create cpp01_topic --build-type ament_cmake --dependencies rclcpp std_msgs base_interfaces_demo
Dependencies also require std_msgs, base_interfaces_demo (which store the custom interfaces we need)
ros2 pkg create + package name (can be written at the beginning or at the end) + --build-type (build type) + ament_cmake / ament_python + --dependencies (dependencies) + rclcpp (ROS2 CPP client) + --node-name (node name) + node name



Publisher

定时器是用来控制发送频率的,定时器里还会执行一个回调函数timer_callback。

count_ is a counter; each time this callback function is executed, count_ is incremented by 1;

The spin function is such that once the program reaches this point, it returns to the above; returning to the above is to invoke the callback function. Without this spin function, our callback function would not be executed.
From now on, whenever we finish creating a node class object pointer, we must pass that pointer into the spin() function.






#include "rclcpp/rclcpp.hpp"
class Talker: public rclcpp::Node
{
public:
Talker():Node("talker_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"发布节点创建!");
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<Talker>());
rclcpp::shutdown();
return 0;
}
At this point the program is in a suspended state and will keep running because of the spin function.
To quit, press Ctrl+C.
The type we want is in std_msgs, so we need to include the header file.

create_publisher()The first entry parameter is the topic name, which is a string
create_publisher()The second entry parameter is related to QoS (Quality of Service); the queue depth is a string of numbers, and for now you can just fill in 10 or 20, etc.
That is, when the network quality is poor and messages cannot be sent, we can first store the data in a queue. Assuming a capacity of 10, at most 10 values can be stored. When the network recovers, we retrieve the data from the queue and send it out.
Other entry parameters have default values and can be ignored for now.
The return value is a pointer to a publisher.




Create a timer. This function has template parameters, but the templates have default values and can be left unset,
Then three entry parameters,
The first entry parameter is the duration, that is, the period;
The second entry parameter is a callback function;
The third entry parameter has a default value, so ignore it for now.

The advantage of using this namespace is that, in the first entry parameter, you can directly fill in time + unit.
If it is 1s, write 1s; if it is 100ms, enter 100ms.


This function also has a return value, which is a pointer related to the timer.



This function has multiple overloads; choose the one that suits you.

To publish an object, you must first create the object.



Convert count to a string and send it.

Because it is of type std::string, we need to convert it to a C-style string.



Try to assign an initial value of 0 to count in the constructor.
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
class Talker: public rclcpp::Node
{
public:
Talker():Node("talker_node_cpp"),count(0)
{
RCLCPP_INFO(this->get_logger(),"发布节点创建!");
publisher_ = this->create_publisher<std_msgs::msg::String>("chatter",10);
timer_ = this->create_wall_timer(1s,std::bind(&Talker::on_timer,this));
}
private:
void on_timer()
{
auto message = std_msgs::msg::String();
message.data = "hello world!" + std::to_string(count++);
RCLCPP_INFO(this->get_logger(),"发布方发布的消息:%s",message.data.c_str());
publisher_->publish(message);
}
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
size_t count;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<Talker>());
rclcpp::shutdown();
return 0;
}

However, it does not necessarily mean that the message was actually published.
Verification method:
ros2 topic echo /xxx

This way, it can be confirmed that the message has been sent to the chatter topic.










#include "rclcpp/rclcpp.hpp"
class Listener: public rclcpp::Node
{
public:
Listener():Node("listener_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"订阅方创建!");
}
};
int main(int argc, char * argv[])
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<Listener>());
rclcpp::shutdown();
return 0;
}

Don't forget to edit the configuration file before compiling.

The dependency package has already been generated automatically; you don't need to worry about it.

Mainly modify these three major parts





There are a total of 5 input parameters, and the latter two input parameters have default values.
The first entry parameter is the topic name, which must be consistent with that of the publisher;
The second entry parameter is QoS, which is quality of service management and queue depth. For now, 10 or 20 can be set arbitrarily; you can look at the publisher's explanation of QoS.
The third entry parameter is a callback function; once data is received, the callback function is triggered.
The return value is a pointer to the subscription object.


std::placeholders::_1 is a placeholder; _1 refers to the first argument. This location should be filled with a message.




#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class Listener: public rclcpp::Node
{
public:
Listener():Node("listener_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"订阅方创建!");
subscription_ = this->create_subscription<std_msgs::msg::String>("chatter",10,std::bind(&Listener::do_callback,this,std::placeholders::_1));
}
private:
void do_callback(const std_msgs::msg::String &msg)
{
RCLCPP_INFO(this->get_logger(),"订阅到的消息是:%s",msg.data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<Listener>());
rclcpp::shutdown();
return 0;
}
4.话题通信_自定义接口信息




Build Dependencies
Execution dependency
The function package group to which the current function package belongs

find_package is to pass the build dependencies over
Then you also need to specify the path of the interface file currently being built (through this setting, the .msg can be converted into the corresponding C++ and Python code).



The filename can be customized, but the first letter must be capitalized



After writing it, test_depend an error occurred.

Just delete it.
The compilation dependencies start with rosidl; let's query them using grep.
ros2 pkg list | grep -i rosidl


We're using this one, just copy it over




In the list, this package group it belongs to is gone, and you need to write it yourself rosidl_interface_packages


This dependency should be the same as the build dependency.
Then we need to generate source code for the interface file.
Need to use the rosidl_generate_interfaces function


colcon build --packages-select base_interfaces_demo


will generate student.hpp code in the install space

generated by the above .msgC++

Then this is the Python source code generated by the .msg

This method can also be used to check whether compilation is normal.
interface is an interface

5.话题通信_实验2(C++)



After creating the source file, configure CMakeLists.




Directly copy the most basic framework over.

Then replace the class name

#include "rclcpp/rclcpp.hpp"
class TalkerStu: public rclcpp::Node
{
public:
TalkerStu():Node("talkerstu_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"发布节点创建!");
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<TalkerStu>());
rclcpp::shutdown();
return 0;
}
For coding standards, change talkerstu_node_cpp to lowercase

Similarly, the subscriber also needs to perform the same operation.

#include "rclcpp/rclcpp.hpp"
class ListenerStu: public rclcpp::Node
{
public:
ListenerStu():Node("listenerstu_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"订阅方创建!");
}
};
int main(int argc, char * argv[])
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<ListenerStu>());
rclcpp::shutdown();
return 0;
}

{
"configurations": [
{
"browse": {
"databaseFilename": "${default}",
"limitSymbolsToIncludedHeaders": false
},
"includePath": [
"/opt/ros/humble/include/**",
"/home/tungchiahui/mysource/ros2src/3.ws01_plumbing/src/base_interfaces_demo/include/**",
"/usr/include/**",
"${workspaceFolder}/",
** "${workspaceFolder}/install/base_interfaces_demo/include/**"
],
"name": "ROS",
"intelliSenseMode": "gcc-x64",
"compilerPath": "/usr/bin/gcc",
"cStandard": "gnu11",
"cppStandard": "c++14"
}
],
"version": 4
}








Do not forget to convert the string to C-style.

#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"
using namespace std::chrono_literals;
class TalkerStu: public rclcpp::Node
{
public:
TalkerStu():Node("talkerstu_node_cpp"),age(0)
{
RCLCPP_INFO(this->get_logger(),"发布节点创建!");
publisher_ = this->create_publisher<base_interfaces_demo::msg::Student>("chatter_stu",10);
timer_ = this->create_wall_timer(500ms,std::bind(&TalkerStu::on_timer_callback,this));
}
private:
void on_timer_callback()
{
auto stu = base_interfaces_demo::msg::Student();
stu.name = "葫芦娃";
stu.age = age;
stu.height = 2.20f;
age++;
publisher_->publish(stu);
RCLCPP_INFO(this->get_logger(),"发布的消息:(%s,%d,%.2f)",stu.name.c_str(),stu.age,stu.height);
}
rclcpp::Publisher<base_interfaces_demo::msg::Student>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
int32_t age;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<TalkerStu>());
rclcpp::shutdown();
return 0;
}



Although the publisher can print logs, it does not mean that the information was sent successfully.

This is the only way to verify that the data has actually been sent out.


#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"
using base_interfaces_demo::msg::Student;
class ListenerStu: public rclcpp::Node
{
public:
ListenerStu():Node("listenerstu_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"订阅方创建!");
Subscription_ = this->create_subscription<Student>("chatter_stu",10,std::bind(&ListenerStu::do_callback,this,std::placeholders::1));
}
private:
void do_callback(const Student &stu)
{
RCLCPP_INFO(this->get_logger(),"订阅的学生信息:name=%s,age=%d,height=%.2f",stu.name.c_str(),stu.age,stu.height);
}
_ rclcpp::Subscription<Student>::SharedPtr Subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<ListenerStu>());
rclcpp::shutdown();
return 0;
}


6.话题通信_rqt查看计算图





Graphical Tool RQT





7.服务通信_理论



There can only be one server, but there can be multiple clients, and each client can send requests to the server. (Of course, there can be multiple servers, but this will cause many logical problems, which is extremely unreasonable and prohibited.)

8.服务通信_实验1_服务端实现(C++)


First start the server, then submit two integers from the client to the server. After reaching the server, the server parses the data, sums them, and returns the result to the client.




ros2 pkg create cpp02_service --build-type ament_cmake --dependencies rclcpp base_interfaces_demo --node-name demo01_server



If demo_interfaces_demo has been used before, then package.xml generally does not need to be configured again.





Remember that the first letter of the filename must be capitalized!











This is another verification method


#include "rclcpp/rclcpp.hpp"
class AddIntsServer: public rclcpp::Node
{
public:
AddIntsServer():Node("add_ints_server_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"服务端节点创建!");
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<AddIntsServer>());
rclcpp::shutdown();
return 0;
}


The server side must remain suspended at all times.

The client exits and returns to the terminal once execution is complete.

Therefore, the client does not need to call the spin function; it can directly create the object.

#include "rclcpp/rclcpp.hpp"
class AddIntsClient: public rclcpp::Node
{
public:
AddIntsClient():Node("add_ints_client_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"客户端节点创建!");
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
// rclcpp::spin(std::make_shared<AddIntsClient>());
auto client = std::make_shared<AddIntsClient>();
rclcpp::shutdown();
return 0;
}
Then you also need to edit the configuration file.

package.xml现在不用修改










There are 4 input parameters, but the last two have default values, so we only need to deal with the first 2.
The first entry parameter is a topic name, a string.
The second entry parameter is the callback function.
The return value is a smart pointer of service type.








#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"
using base_interfaces_demo::srv::AddInts;
class AddIntsServer: public rclcpp::Node
{
public:
AddIntsServer():Node("add_ints_server_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"服务端节点创建!");
server_ = this->create_service<base_interfaces_demo::srv::AddInts>("add_ints",std::bind(&AddIntsServer::add_callback,this,std::placeholders::_1,std::placeholders::2));
}
private:
void add_callback(const AddInts::Request::SharedPtr req,AddInts::Response::SharedPtr res)
{
res->sum = req->num1 + (*req).num2;
RCLCPP_INFO(this->get_logger(),"%d + %d = %d",req->num1,req->num2,res->sum);
}
_ rclcpp::Service<base_interfaces_demo::srv::AddInts>::SharedPtr server_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<AddIntsServer>());
rclcpp::shutdown();
return 0;
}


Because our client has not been written yet, for now we will temporarily use the small tool ros2 service call to check the situation on the server side.

ros2 service call /add_ints base_interfaces_demo/srv/AddInts "{'num1': 10,'num2': 30}"
ros2 service call + topic name + interface data type + JSON code (which can also be understood as YAML format)
This JSON code (YAML format) format: "{'name of the first number': space + corresponding value,'name of the second number': space + corresponding numeric value}"
9.服务通信_实验1_客户端实现(C++)




When running, two integer data items follow it,
Therefore, this argc should be equal to 3.



argv: receives the argc parameter returned at compile time
argc is the total number of command-line arguments
argvis argc arguments, where the 0th argument is the full name of the program, and subsequent arguments


You must ensure that the server is running and that the client can connect to the server. If the server is not running, the data sent will be lost. However, in general, information communicated using a service is relatively important, so it must not be lost.

After the client finishes sending data, a response is generated, which is directly returned as the function's return value.






#include "rclcpp/rclcpp.hpp"
class AddIntsClient: public rclcpp::Node
{
public:
AddIntsClient():Node("add_ints_client_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"客户端节点创建!");
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
// rclcpp::spin(std::make_shared<AddIntsClient>());
auto client = std::make_shared<AddIntsClient>();
rclcpp::shutdown();
return 0;
}

This section should be placed before node initialization to prevent performing resource-intensive operations before the check.

Because RCLCPP_INFO is before node creation, it cannot be obtained using get_logger through classes and instantiation, that is, it cannot be obtained using the this pointer and node smart pointer.
So we adopt the following approach:

This approach uses get_logger in rclcpp, but the log needs a name, which is placed in the entry parameter. Let's call it rclcpp.
#include "rclcpp/rclcpp.hpp"
class AddIntsClient: public rclcpp::Node
{
public:
AddIntsClient():Node("add_ints_client_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"客户端节点创建!");
}
};
int main(int argc, char ** argv)
{
if(argc != 3)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"请提交两个整形数字!");
return 1;
}
rclcpp::init(argc,argv);
auto client = std::make_shared<AddIntsClient>();
rclcpp::shutdown();
return 0;
}



If I do not submit any parameters and just press Enter, then this is an exception, and the return value of the main function is not 0.


You can also change INFO to ERROR.

#include "rclcpp/rclcpp.hpp"
class AddIntsClient: public rclcpp::Node
{
public:
AddIntsClient():Node("add_ints_client_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"客户端节点创建!");
}
};
int main(int argc, char ** argv)
{
if(argc != 3)
{
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"请提交两个整形数字!");
return 1;
}
rclcpp::init(argc,argv);
auto client = std::make_shared<AddIntsClient>();
rclcpp::shutdown();
return 0;
}

There are a total of 3 input parameters,
The first entry parameter is the topic name, which is a string;
The second entry parameter and the third entry parameter have default values, so ignore them for now;
The return value is a smart pointer to the client.











Once Ctrl+C is closed, it will frantically flood INFO logs, and the program cannot be stopped.


Pressing Ctrl+Z again can stop the program.


Fix the above bug Ctrl+C:

rclcpp::ok() is used to determine whether the current program is running normally. If it is running normally, it returns true; if it is not running normally, it returns false. For example, pressing Ctrl+C means it is not running normally.
When rclcpp::ok() != true, ctrl+c has been pressed.

This directly allows the function to end.


At this point, pressing ctrl+c will cause many errors to appear
This is because
this->get_logger()
client->get_logger()
rclcpp::get_logger()
different
This exception is related to context. During initialization, a context object is created, which is essentially a container. You can put data into the container and also retrieve data from it.
Currently, if our connection fails, print the log.
Pressing ctrl+c will terminate our ROS2 program. Resources must be released, for example, the context must be closed. At this point, the context has already been closed. In this way, if we then try to obtain logs from client and this, it will no longer work, so it is recommended to use rclcpp::get_logger().
Because the call to rclcpp::get_logger() is unrelated to the context.

This way, the program works properly!
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"
using base_interfaces_demo::srv::AddInts;
using namespace std::chrono_literals;
class AddIntsClient: public rclcpp::Node
{
public:
AddIntsClient():Node("add_ints_client_node_cpp")
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"客户端节点创建!");
client_ = this->create_client<AddInts>("add_ints");
}
bool connect_server()
{
while(client_->wait_for_service(1s) != true)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务连接中!");
if (rclcpp::ok() != true)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"强行终止客户端!");
return false;
}
}
return true;
}
private:
rclcpp::Client<AddInts>::SharedPtr client_;
};
int main(int argc, char ** argv)
{
if(argc != 3)
{
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"请提交两个整形数字!");
return 1;
}
rclcpp::init(argc,argv);
auto client = std::make_shared<AddIntsClient>();
bool flag = client->connect_server();
if (flag != true)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务器连接失败,程序退出!");
return 0;
}
rclcpp::shutdown();
return 0;
}




Now that we have the return value type, let's paste it over,
Because using base_interfaces_demo::srv::AddInts, it can be abbreviated to AddInts



In the main function, the function must be called.

atoi() converts data into an integer

We also need to handle responses, and there are 3 responses
The first is an interrupt, the second is a success, and the third is a timeout;
We generally only consider success; the other two cases are both regarded as failure.

The first entry parameter is the node's smart pointer
The second entry parameter is future
The third entry parameter has a default value, so ignore it for now.





#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"
using base_interfaces_demo::srv::AddInts;
using namespace std::chrono_literals;
class AddIntsClient: public rclcpp::Node
{
public:
AddIntsClient():Node("add_ints_client_node_cpp")
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"客户端节点创建!");
client_ = this->create_client<AddInts>("add_ints");
}
bool connect_server()
{
while(client_->wait_for_service(1s) != true)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务连接中!");
if (rclcpp::ok() != true)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"强行终止客户端!");
return false;
}
}
return true;
}
rclcpp::Client<AddInts>::FutureAndRequestId send_request(int32_t num1,int32_t num2)
{
/*
返回值 rclcpp::Client<base_interfaces_demo::srv::AddInts>::FutureAndRequestId
入口参数 async_send_request(std::shared_ptr<base_interfaces_demo::srv::AddInts_Request> request) //其实就相当于AddInts::Request类型
*/
auto request = std::make_sharedautolinkAddInts::Requestautolink();
request->num1 = num1;
request->num2 = num2;
return client_->async_send_request(request);
}
private:
rclcpp::Client<AddInts>::SharedPtr client_;
};
int main(int argc, char ** argv)
{
if(argc != 3)
{
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"请提交两个整形数字!");
return 1;
}
rclcpp::init(argc,argv);
auto client = std::make_shared<AddIntsClient>();
bool flag = client->connect_server();
if (flag != true)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务器连接失败,程序退出!");
return 0;
}
auto future = client->send_request(atoi(argv[1]),atoi(argv[2]));
if (rclcpp::spin_until_future_complete(client,future) == rclcpp::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(client->get_logger(),"响应成功! sum = %d",future.get()->sum);
}
else
{
RCLCPP_INFO(client->get_logger(),"响应失败!");
}
rclcpp::shutdown();
return 0;
}
10.动作通信_理论

Let B continuously return the current robot status information to A; such behavioral communication better matches our navigation requirements for controlling the robot.

If you input 10, it will sum all numbers from 1 to 10, and it will iterate through all numbers from 1 to 10 and accumulate them. Accumulation takes time; assume that each accumulation takes one second,
Then, to make it easier to see how the program is running, send an INFO message at each accumulation step to indicate the current progress.

You can cancel the task while it is in progress.
Step 1: The client sends the target data to the server.
Step 2: The server evaluates the target data and feeds back the evaluation result (whether the target can be achieved) to the client.
Third step: the client then sends the final confirmed target data to the server.
Step 4: The server continuously feeds back the process data of execution to the client.
Step 5: After completion, the server feeds back the final result to the client






ros2 pkg create cpp03_action --build-type ament_cmake --dependencies rclcpp rclcpp_action base_interfaces_demo --node-name demo01_action_server


At the very top is the request data,
In the middle is the data of the final response result,
At the very bottom is the data for the continuous feedback.




depend is the integration of build depend, exe depend, and export depend.








ros2 interface show base_interfaces_demo/action/Progress
11.动作通信_实验1_服务端实现(C++)

#include "rclcpp/rclcpp.hpp"
class ProgressActionServer: public rclcpp::Node
{
public:
ProgressActionServer():Node("progress_action_server_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"action服务端创建!");
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<ProgressActionServer>());
rclcpp::shutdown();
return 0;
}

#include "rclcpp/rclcpp.hpp"
class ProgressActionClient: public rclcpp::Node
{
public:
ProgressActionClient():Node("progress_action_server_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"action客户端创建!");
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<ProgressActionClient>());
rclcpp::shutdown();
return 0;
}







There are two templates, and we only need to set the action, which is our action interface type.
The first parameter is node; within a class, use the this pointer,
The second parameter is the topic, a string,
The third parameter is a callback function used to process the target value.
The fourth parameter is a callback function used to handle the cancellation request,
The fifth parameter is that after receiving the target value, the callback function generates continuous feedback,
The sixth and seventh parameters have default values, so ignore them for now,
The return value is an action smart pointer.






Goal_callback解析:
The first parameter is GoalUUID,
The second parameter is the Goal under our action interface,
The return value is goalresponse, using the namespace rclcpp_action, with three constants encapsulated underneath,
The first is to receive and execute immediately,
The second is receiving and deferring execution,
The third is refusal.





The error occurred because a placeholder was not added.


#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "base_interfaces_demo/action/progress.hpp"
using base_interfaces_demo::action::Progress;
using std::placeholders::_1;
using std::placeholders::_2;
class ProgressActionServer: public rclcpp::Node
{
public:
ProgressActionServer():Node("progress_action_server_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"action服务端创建!");
/*
rclcpp_action::Server<ActionT>::SharedPtr create_server<ActionT,
NodeT>(NodeT node,
const std::string &name,
rclcpp_action::Server<ActionT>::GoalCallback handle_goal,
rclcpp_action::Server<ActionT>::CancelCallback handle_cancel,
rclcpp_action::Server<ActionT>::AcceptedCallback handle_accepted,
const rcl_action_server_options_t &options = rcl_action_server_get_default_options(),
rclcpp::CallbackGroup::SharedPtr group = nullptr)
*/
server_ = rclcpp_action::create_server<Progress>(
this,
"get_sum_topic",
std::bind(&ProgressActionServer::handle_goal_callback,this,_1,_2),
std::bind(&ProgressActionServer::handle_cancel_callback,this,_1),
std::bind(&ProgressActionServer::handle_accepted_callback,this,_1)
);
}
//std::function<GoalResponse(const GoalUUID &, std::shared_ptr<const typename ActionT::Goal>)>
rclcpp_action::GoalResponse handle_goal_callback(const rclcpp_action::GoalUUID &, std::shared_ptr<const Progress::Goal>)
{
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
//std::function<CancelResponse(std::shared_ptr<ServerGoalHandle<ActionT>>)>
rclcpp_action::CancelResponse handle_cancel_callback(std::shared_ptr<rclcpp_action::ServerGoalHandle<Progress>> goal_handle)
{
return rclcpp_action::CancelResponse::ACCEPT;
}
//std::function<void (std::shared_ptr<ServerGoalHandle<ActionT>>)>
void handle_accepted_callback(std::shared_ptr<rclcpp_action::ServerGoalHandle<Progress>> goal_handle)
{
}
private:
rclcpp_action::Server<Progress>::SharedPtr server_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<ProgressActionServer>());
rclcpp::shutdown();
return 0;
}




ros2 action send_goal /get_sum_topic base_interfaces_demo/action/Progress -f "{'num': 10}"
ros2 action send_goal /topic name + interface type + -f + parameter
\-f is continuous feedback, meaning continuous feedback can be obtained.

Send target value is 10
Then we set an ID for our client, because there may be multiple clients accessing this server, so we need to set a unique ID for each client.
Then the result is 0
Because we haven't written anything in our program yet.

The uuid is the client ID, and it is not used at this point, so use (void)uuid, because if it is not used, the compiler may issue a warning.





Our task can be canceled normally, so just return accept; do different things in the function according to different task requirements.


Because generating our continuous feedback and final response is a time-consuming operation, to avoid blocking the main logic, it is recommended to start a separate thread.





The API for generating continuous feedback requires passing parameters, and the parameter passed in is the Feedback object in Progress.

get_goal() can obtain the target value


Because this is a time-consuming operation, to make the effect visible, we set a sleep during each loop iteration.

1.0 refers to 1 Hz, that is, executing once every 1 second.

Because it is 1Hz, our rate.sleep() will sleep for 1 second each time;

API for generating the final result requires passing parameters; the passed parameter is the Result object in Progress.

Return value of the ok() function: if it runs normally, it returns true; if it does not run normally, it returns false.










bug: After we terminate the client, the server does not stop running. The server should keep running until the program ends.

Bug resolution approach:
After receiving the cancellation request, it interrupts my main logic, which is execute_callback being closed,

This function returns a Boolean value: it returns true if a cancellation request has been received, otherwise it returns false.

After we cancel, we can actually still reflect the final result to the client using the canceled function.



The result definition should be placed first.



#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "base_interfaces_demo/action/progress.hpp"
using base_interfaces_demo::action::Progress;
using std::placeholders::_1;
using std::placeholders::_2;
class ProgressActionServer: public rclcpp::Node
{
public:
ProgressActionServer():Node("progress_action_server_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"action服务端创建!");
/*
rclcpp_action::Server<ActionT>::SharedPtr create_server<ActionT,
NodeT>(NodeT node,
const std::string &name,
rclcpp_action::Server<ActionT>::GoalCallback handle_goal,
rclcpp_action::Server<ActionT>::CancelCallback handle_cancel,
rclcpp_action::Server<ActionT>::AcceptedCallback handle_accepted,
const rcl_action_server_options_t &options = rcl_action_server_get_default_options(),
rclcpp::CallbackGroup::SharedPtr group = nullptr)
*/
server_ = rclcpp_action::create_server<Progress>(
this,
"get_sum_topic",
std::bind(&ProgressActionServer::handle_goal_callback,this,_1,_2),
std::bind(&ProgressActionServer::handle_cancel_callback,this,_1),
std::bind(&ProgressActionServer::handle_accepted_callback,this,_1)
);
}
//std::function<GoalResponse(const GoalUUID &, std::shared_ptr<const typename ActionT::Goal>)>
rclcpp_action::GoalResponse handle_goal_callback(const rclcpp_action::GoalUUID &uuid, std::shared_ptr<const Progress::Goal> goal)
{
(void)uuid;
if(goal->num <= 1)
{
RCLCPP_INFO(this->get_logger(),"提交的目标值必须大于1!");
return rclcpp_action::GoalResponse::REJECT;
}
RCLCPP_INFO(this->get_logger(),"提交的目标值合法!");
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
//std::function<CancelResponse(std::shared_ptr<ServerGoalHandle<ActionT>>)>
rclcpp_action::CancelResponse handle_cancel_callback(std::shared_ptr<rclcpp_action::ServerGoalHandle<Progress>> goal_handle)
{
(void)goal_handle;
RCLCPP_INFO(this->get_logger(),"接收到任务取消请求!");
return rclcpp_action::CancelResponse::ACCEPT;
}
//std::function<void (std::shared_ptr<ServerGoalHandle<ActionT>>)>
void execute_callback(std::shared_ptr<rclcpp_action::ServerGoalHandle<Progress>> goal_handle)
{
//void publish_feedback(std::shared_ptr<base_interfaces_demo::action::Progress_Feedback> feedback_msg)
//goal_handle->publish_feedback();
int num = goal_handle->get_goal()->num;
int sum = 0;
auto feedback = std::make_sharedautolinkProgress::Feedbackautolink();
auto result = std::make_sharedautolinkProgress::Resultautolink();
rclcpp::Rate rate(1.0);
for (int32_t i = 1; i <= num; i++)
{
sum += i;
double progress = i / (double)num;
feedback->progress = progress;
goal_handle->publish_feedback(feedback);
RCLCPP_INFO(this->get_logger(),"连续反馈中,当前进度为:%.2f",progress);
if(goal_handle->is_canceling() == true)
{
result->sum = sum;
goal_handle->canceled(result);
RCLCPP_INFO(this->get_logger(),"任务被取消了!");
return;
}
rate.sleep();
}
//void succeed(std::shared_ptr<base_interfaces_demo::action::Progress_Result> result_msg)
//goal_handle->succeed();
if(rclcpp::ok() == true)
{
result->sum = sum;
goal_handle->succeed(result);
RCLCPP_INFO(this->get_logger(),"最终响应结果为:%d",sum);
}
}
void handle_accepted_callback(std::shared_ptr<rclcpp_action::ServerGoalHandle<Progress>> goal_handle)
{
std::thread(std::bind(&ProgressActionServer::execute_callback,this,goal_handle)).detach();
}
private:
rclcpp_action::Server<Progress>::SharedPtr server_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<ProgressActionServer>());
rclcpp::shutdown();
return 0;
}
12.动作通信_实验1_客户端实现(C++)

This red line is encapsulated by action_client for us, so we do not need to worry about it.

#include "rclcpp/rclcpp.hpp"
class ProgressActionClient: public rclcpp::Node
{
public:
ProgressActionClient():Node("progress_action_server_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"action客户端创建!");
}
};
int main(int argc, char ** argv)
{
if(argc != 2)
{
RCLCPP_INFO(rclcpp::get_logger("rclcppp"),"请输入一个整形数字!");
return 1;
}
rclcpp::init(argc,argv);
rclcpp::spin(std::make_shared<ProgressActionClient>());
rclcpp::shutdown();
return 0;
}




The first entry parameter is node
The second entry parameter is the topic name, a string
The third entry parameter and the fourth entry parameter both have default values.
The return value is a client smart pointer.




It is necessary to split one step into two steps.

Then call the send_goal function again.


async_send_goal is to asynchronously send the target value

The first entry parameter is the target value in our interface file
The second entry parameter is the send target options object. We can set some callback functions that we need to handle after this target is sent.
The return value is




Let's not worry about anything else for now; just define the function casually, with void return value and void entry parameters, so that the program doesn't report errors for the time being.



Learned,
The return value of GoalResponseCallback is void, and the entry parameter is goalhandle. The goalhandle is above in this diagram; it is clientgoalhandle, and then this clientgoalhandle belongs to the rclcpp_action workspace.





The return value is void
The first entry parameter is clientgoalhandle
The second entry parameter is feedback






When sending a value to the server, the server must first obtain the target value for judgment, determine whether the target value can be accepted or needs to be rejected, and then respond to the client with the processing result.
If this target value is processable, then there is content in goal_handle;
If it cannot be handled, then goal_handle is a nullptr null pointer.




Next, process the feedback data:

If we only parse the feedback data, then goal_handle is not needed.



You need to use two % to escape %, as shown in the figure above, which is a case of printing percentage data.
Assuming progress_int is 50, it will output 50%.
If you only want to print a single %, you need to use %% to escape it.


The callback function may lose data. This is normal.
Final Response:
The status of this result—the final result—is not necessarily fixed; the task may have been canceled or terminated, or it may have run normally.
Therefore, we need to determine the state through the status code.

The first one was forcibly terminated
The second is to cancel
The third is success
The fourth is unknown



#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "base_interfaces_demo/action/progress.hpp"
using base_interfaces_demo::action::Progress;
using namespace std::chrono_literals;
using std::placeholders::_1;
using std::placeholders::_2;
class ProgressActionClient: public rclcpp::Node
{
public:
ProgressActionClient():Node("progress_action_server_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"action客户端创建!");
/*
rclcpp_action::Client<ActionT>::SharedPtr create_client<ActionT,
NodeT>(NodeT node, const std::string &name,
rclcpp::CallbackGroup::SharedPtr group = nullptr,
const rcl_action_client_options_t &options = rcl_action_client_get_default_options())
*/
client_ = rclcpp_action::create_client<Progress>(this,"get_sum_topic");
}
void send_goal(int32_t num)
{
if(client_->wait_for_action_server(1s) != true)
{
RCLCPP_ERROR(this->get_logger(),"服务连接失败!");
return;
}
/*
std::shared_future<rclcpp_action::ClientGoalHandle<base_interfaces_demo::action::Progress>::SharedPtr>
async_send_goal(const base_interfaces_demo::action::Progress::Goal &goal,
const rclcpp_action::Client<base_interfaces_demo::action::Progress>::SendGoalOptions &options)
*/
auto goal = Progress::Goal();
goal.num = num;
rclcpp_action::Client<Progress>::SendGoalOptions options;
options.goal_response_callback = std::bind(&ProgressActionClient::goal_response_callback,this,_1);
options.feedback_callback = std::bind(&ProgressActionClient::feedback_callback,this,_1,_2);
options.result_callback = std::bind(&ProgressActionClient::result_callback,this,_1);
auto future = client_->async_send_goal(goal,options);
}
void goal_response_callback(rclcpp_action::ClientGoalHandle<Progress>::SharedPtr goal_handle)
{
if(goal_handle == nullptr)
{
RCLCPP_INFO(this->get_logger(),"目标请求被服务端拒绝!");
}
else
{
RCLCPP_INFO(this->get_logger(),"目标处理中!");
}
}
void feedback_callback(rclcpp_action::ClientGoalHandle<Progress>::SharedPtr goal_handle,std::shared_ptr<const Progress::Feedback> feedback)
{
(void)goal_handle;
double progress = feedback->progress;
int progress_int = (int) (progress * 100);
RCLCPP_INFO(this->get_logger(),"当前进度为:%d%%",progress_int);
}
void result_callback(const rclcpp_action::ClientGoalHandle<Progress>::WrappedResult & result)
{
if (result.code == rclcpp_action::ResultCode::SUCCEEDED)
{
RCLCPP_INFO(this->get_logger(),"最终结果为:%d",result.result->sum);
}
else if(result.code == rclcpp_action::ResultCode::ABORTED)
{
RCLCPP_INFO(this->get_logger(),"过程被中断!");
}
else if(result.code == rclcpp_action::ResultCode::CANCELED)
{
RCLCPP_INFO(this->get_logger(),"任务被取消!");
}
else
{
RCLCPP_INFO(this->get_logger(),"未知异常!");
}
}
private:
rclcpp_action::Client<Progress>::SharedPtr client_;
};
int main(int argc, char ** argv)
{
if(argc != 2)
{
RCLCPP_ERROR(rclcpp::get_logger("rclcppp"),"请输入一个整形数字!");
return 1;
}
rclcpp::init(argc,argv);
auto node = std::make_shared<ProgressActionClient>();
node->send_goal(atoi(argv[1]));
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}


At this point, when we cancel the client, the server is still running. The ctrl+c here only terminates our client; it does not instruct our client to send a cancel request command. We can only complete the sending of the cancel request command by capturing our keyboard input.
Fix bug:
Not fixed yet
// 发送取消请求auto future_cancel = client_->async_cancel_goal(goal_handle);
rclcpp::spin_until_future_complete(this->get_node_base_interface(), future_cancel);
if (future_cancel.wait_for(1s) == std::future_status::ready)
{
RCLCPP_INFO(this->get_logger(), "终止请求已发送!");
}
else
{
RCLCPP_ERROR(this->get_logger(), "无法发送终止请求...");
}
13.参数服务_理论与API介绍(C++)


Of course, there are also special cases, such as setting the parameter to private.


Other communications require you to create your own interface files, but for the parameter service you do not need to create interface files yourself; ROS2 has already encapsulated the API, so we only need to call the API.



I just want to demonstrate the API, so first create the parameter function package to demonstrate the API, and do not create the client and server for now.



rclcpp::parameter object(key, value);







This function assigns a value to a parameter and has 18 overloads for various types.
Here, empty means no value is assigned, so there is only a key and no value.
14.参数服务_实验1_服务端(C++)


There is a difference here: a second argument is passed in Node, and this statement is specifically used to allow parameter deletion. undeclared removes the declaration.





There are only query and modification API interfaces, and no APIs for addition and deletion; this is ROS2 based on security considerations.




#include "rclcpp/rclcpp.hpp"
class ParamServer: public rclcpp::Node
{
public:
ParamServer():Node("param_server_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"参数服务端搭建!");
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
auto node = std::make_shared<ParamServer>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
#include "rclcpp/rclcpp.hpp"
class ParamClient: public rclcpp::Node
{
public:
ParamClient():Node("param_client_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"参数客户端搭建!");
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
auto node = std::make_shared<ParamClient>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}







Chained programming.
An ordinary node can serve as the parameter server; there is no need to separately create a parameter server.


#include "rclcpp/rclcpp.hpp"
class ParamServer: public rclcpp::Node
{
public:
ParamServer():Node("param_server_node_cpp",rclcpp::NodeOptions().allow_undeclared_parameters(true))
{
RCLCPP_INFO(this->get_logger(),"参数服务端搭建!");
}
void create_param()
{
RCLCPP_INFO(this->get_logger(),"-------------增操作--------------");
}
void get_param()
{
RCLCPP_INFO(this->get_logger(),"-------------查操作--------------");
}
void update_param()
{
RCLCPP_INFO(this->get_logger(),"-------------改操作--------------");
}
void delete_param()
{
RCLCPP_INFO(this->get_logger(),"-------------删操作--------------");
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
auto node = std::make_shared<ParamServer>();
node->create_param();
node->get_param();
node->update_param();
node->delete_param();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}



ros2 param list
Query all parameters in all nodes

ros2 param get /节点名称 参数键名 来查看参数的值

The value of a parameter can be queried by key.

A function with a plural form can obtain certain parameter objects through a container composed of keys.

to determine whether the parameter exists; the entry parameter is also the key, and the return value is a boolean.







A parameter object needs to be passed in.

We can simply overwrite the old value.


Parameters can also be created via set_parameter, but allow_undeclared_parameters (true) must be declared.





Parameters declared in this way cannot be deleted; only those that are undeclared but set can be deleted.


#include "rclcpp/rclcpp.hpp"
class ParamServer: public rclcpp::Node
{
public:
ParamServer():Node("param_server_node_cpp",rclcpp::NodeOptions().allow_undeclared_parameters(true))
{
RCLCPP_INFO(this->get_logger(),"参数服务端搭建!");
}
void create_param()
{
RCLCPP_INFO(this->get_logger(),"-------------增操作--------------");
this->declare_parameter("car_name","ER");
this->declare_parameter("width",1.55);
this->declare_parameter("wheels",5);
this->set_parameter(rclcpp::Parameter("height",2.00));
}
void get_param()
{
RCLCPP_INFO(this->get_logger(),"-------------查操作--------------");
auto car = this->get_parameter("car_name");
RCLCPP_INFO(this->get_logger(),"key = %s,value = %s",car.get_name().c_str(),car.as_string().c_str());
auto params = this->get_parameters({"car_name","width","wheels"});
for(auto &¶m : params)
{
RCLCPP_INFO(this->get_logger(),"key = %s,value = %s",param.get_name().c_str(),param.value_to_string().c_str());
}
bool car_name_flag = this->has_parameter("car_name");
bool height_flag = this->has_parameter("height");
RCLCPP_INFO(this->get_logger(),"是否包含car_name? 答案:%d",car_name_flag);
RCLCPP_INFO(this->get_logger(),"是否包含height? 答案:%d",height_flag);
}
void update_param()
{
RCLCPP_INFO(this->get_logger(),"-------------改操作--------------");
this->set_parameter(rclcpp::Parameter("width",1.85));
RCLCPP_INFO(this->get_logger(),"width = %.2f",this->get_parameter("width").as_double());
}
void delete_param()
{
RCLCPP_INFO(this->get_logger(),"-------------删操作--------------");
// this->undeclare_parameter("car_name");
// RCLCPP_INFO(this->get_logger(),"是否包含car_name? 答案:%d",this->has_parameter("car_name"));
this->undeclare_parameter("height");
RCLCPP_INFO(this->get_logger(),"是否包含height? 答案:%d",this->has_parameter("height"));
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
auto node = std::make_shared<ParamServer>();
node->create_param();
node->get_param();
node->update_param();
node->delete_param();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
15.参数服务_实验1_客户端(C++)










The first entry parameter is the client node object,
The second entry parameter is the node name of the server to which a connection is to be established.

Return true if the connection is established within 1 second; return false if the connection times out after 1 second.


#include "rclcpp/rclcpp.hpp"
using namespace std::chrono_literals;
class ParamClient: public rclcpp::Node
{
public:
ParamClient():Node("param_client_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"参数客户端搭建!");
param_client_ = std::make_sharedautolinkrclcpp::SyncParametersClientautolink(this,"param_server_node_cpp");
}
bool connect_server()
{
while(param_client_->wait_for_service(1s) != true)
{
if(rclcpp::ok() != true)
{
return false;
}
RCLCPP_INFO(this->get_logger(),"服务连接中!");
}
return true;
}
void get_param()
{
RCLCPP_INFO(this->get_logger(),"-----------参数查询操作-------------");
}
void update_param()
{
RCLCPP_INFO(this->get_logger(),"-----------参数更新操作-------------");
}
private:
rclcpp::SyncParametersClient::SharedPtr param_client_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
auto client = std::make_shared<ParamClient>();
bool flag = client->connect_server();
if(!flag)
{
return 0;
}
client->get_param();
client->update_param();
client->get_param();
// rclcpp::spin(client);
rclcpp::shutdown();
return 0;
}



These topics are all under our node name.




Use enhanced for






For the input parameter, fill in the container of the parameter object.


We can not only modify values, but also create new parameters, but we must ensure that the server side has called undeclared...

