Four Major Communications
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, and the second window is the client.


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



Interface-oriented, with consistent topics and data carriers, allows for seamless integration.


Topic Communication: Can only transmit data in one direction
Service Communication: Bidirectional communication where both parties can act as 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, with a final response sent from the server to the client, but it also continuously sends feedback to the client in between.
Parameter Service: The parameter service is based on service communication. A parameter client first sends a request, then retrieves data from the parameter service's data pool. It can also modify data in the pool, but cannot delete it.







Parameter communication does not require you to define your own interface file — the system will generate one automatically. However, developers cannot see this file, as 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 C++ client) + --node-name (node name) + node name

Create a functional package in src.

Topic Communication_Theory




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


A publisher sends data, and both subscribers will receive the data.



Topic Communication Experiment 1 (C++)






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



Publisher

The timer is used to control the sending frequency, and a callback function timer_callback is executed within the timer.

count_ is a counter; each time this callback function is executed, count_ increments by one.

The spin function, once the program reaches this point, returns to the top. It returns to the top in order to call the callback function. Without this spin function, our callback function would not be executed.
From now on, whenever we create 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 exit, press Ctrl+C.
The type we want is in std_msgs, so we need to add the header file.

The first input parameter of create_publisher() is the topic name, which is a string.
The second input parameter of create_publisher() is related to QoS (Quality of Service). It is a queue depth represented by a number. For now, you can fill in 10 or 20, etc.
In other words, when the network quality is poor and messages cannot be sent, we can temporarily store the data in a queue. For example, if the queue is set to 10, it can hold up to 10 items. When the network recovers, we retrieve data from the queue and send it out.
Other entry parameters have default values, so you can ignore them for now.
The return value is a pointer to a publisher.




Create a timer. This function has a template, but the template has default values so it can be left unset.
Then three entry parameters,
The first input parameter is the duration, i.e., the period.
The second entry parameter is a callback function.
The third input 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 the time and unit.
If it's 1s, write 1s; if it's 100ms, fill in 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 your needs.

To publish an object, you must first create it.



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 whenever possible.
#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;
}

But it doesn't necessarily mean the message was actually published.
Verification method:
ros2 topic echo /xxx

This is how you can confirm that the message was published 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 been automatically generated, so you don't need to worry about it.

Mainly revise these three major parts.





There are a total of 5 input parameters, with the last two having default values.
The first entry parameter is the topic name, which must match the publisher's.
The second input parameter is QoS, which stands for Quality of Service management, queue depth. You can temporarily set it to 10 or 20 arbitrarily; refer to the QoS explanation on the publisher side.
The third entry parameter is a callback function. Once data is received, this callback function is triggered.
The return value is a pointer to a subscription object.


std::placeholders::_1 is a placeholder, where _1 refers to the first argument. In this context, a message should normally be provided.




#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;
}
Topic Communication - Custom Interface Messages




Build dependencies
Install dependencies
The functional package group to which the current functional package belongs

find_package is used to pass along build dependencies.
Then you also need to specify the path to the interface file currently being built (with this setting, the .msg file 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, the test_depend reported an error.

Just delete it.
The build dependencies start with rosidl. Let's check with grep.
ros2 pkg list | grep -i rosidl


We are using this one, just copy it directly.




In the list, the corresponding function package group is no longer present, and you need to write rosidl_interface_packages yourself.


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 the student.hpp code in the install space.

Here's the C++ code to generate the .msg file content you provided:
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::string msgContent = R"(# 以上.msg生成C++的
# This is a sample .msg file for ROS
string data
int32 number
float64 value)";
std::ofstream outFile("sample.msg");
if (outFile.is_open()) {
outFile << msgContent;
outFile.close();
std::cout << "sample.msg file generated successfully." << std::endl;
} else {
std::cerr << "Failed to create sample.msg file." << std::endl;
return 1;
}
return 0;
}
This C++ program:
- Defines the
.msgfile content as a raw string literal - Opens a file named
sample.msgfor writing - Writes the content to the file
- Closes the file and reports success or failure
The generated sample.msg file will contain:
# 以上.msg生成C++的
# This is a sample .msg file for ROS
string data
int32 number
float64 value

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

You can also use this method to check whether the compilation is normal.
interface is an interface

Topic Communication Experiment 2 (C++)



After creating the source file, you need to configure CMakeLists.




Copy the most basic framework directly.

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 this 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
}








Don't 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 the information was sent out normally.

Only this kind of verification can truly confirm that the data has 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;
}


Topic Communication: Viewing the Computation Graph with rqt





Graphical tool RQT





Service Communication_Theory



Only one server is allowed, but there can be multiple clients, each of which can send requests to the server. (Of course, multiple servers are technically possible, but that would introduce many logical issues and is highly unreasonable, so it is prohibited.)

Service Communication_Experiment 1_Server Implementation (C++)


First start the server, then submit two integers from the client. After the server receives them, it will parse the data, compute the sum, and return the result to the client.




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



If you have used demo_interfaces_demo before, you generally don't need to configure package.xml again.





Remember the first letter of the filename should 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 must remain running continuously.

The client returns to the terminal once execution is complete.

So the client doesn't need to call the spin function; it just creates the object directly.

#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.

There is no need to modify package.xml for now.










There are 4 input parameters, but the last two have default values, so we only need to worry about the first two.
The first entry parameter is a topic name, a string.
The second input parameter is a 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;
}


Since we haven't written the client yet, we'll temporarily use the ros2 service call tool to check the server's status.

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 value}"
Service Communication_Experiment 1_Client Implementation (C++)




When running, it is followed by two integer data.
So this argc should be equal to 3.



argv: receives the argc parameters returned at compile time
argc is the total number of command-line arguments.
argv contains argc parameters, where the 0th parameter is the full name of the program, and the subsequent parameters are the remaining arguments.


The server must be running and the client must be able to connect to it. If the server is not running, any data sent will be lost. However, service communication is typically used for important information that must not be lost.

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






#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 making a judgment.

Because RCLCPP_INFO is placed before the node is created, it cannot use class and instantiation methods to call get_logger, meaning it cannot use the this pointer or the node's smart pointer to retrieve it.
Therefore, we adopt the following approach:

This approach uses get_logger from rclcpp, but it requires giving the logger a name, which is passed as an input 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 don't submit any parameters and just press Enter, this is an exception, and the main function's return value 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 entry parameters.
The first input parameter is the topic name, which is a string.
The second and third input parameters have default values, so ignore them for now.
The return value is a smart pointer for the client.











Once Ctrl+C is pressed to close, it will flood with INFO messages and the program cannot be stopped.


Press Ctrl+Z again to stop the program.


Fix the Ctrl+C bug mentioned above:

rclcpp::ok() checks whether the current program is running normally. If it is running normally, it returns true; if not, it returns false. For example, pressing Ctrl+C causes abnormal termination.
When rclcpp::ok() != true, that means Ctrl+C has been pressed.

This directly allows the function to end.


At this point, pressing Ctrl+C will throw many errors.
This is because
this->get_logger()
client->get_logger()
rclcpp::get_logger()
of different
This exception is related to the context. During initialization, a context object is created, which acts as a container. Data can be placed into this container, and data can also be retrieved from it.
Currently, if our connection fails, we print a log.
Pressing ctrl+c will terminate our ROS2 program. To release resources, such as closing the context, the context will already be shut down by then. As a result, we can no longer retrieve logs from the client and this, so it is recommended to use rclcpp::get_logger().
Because the call to rclcpp::get_logger() has no relation to the context.

The program works correctly 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;
}
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 of using base_interfaces_demo::srv::AddInts, it can be abbreviated as AddInts.



In the main function, you need to call the function.

atoi() converts data into an integer.

We also need to handle the response. There are 3 responses.
The first is interrupt, the second is success, and the third is timeout.
We generally only consider success, and treat the other two cases as failures.

The first entry parameter is the smart pointer of the node.
The second input parameter is the future.
The third input 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;
}
Action Communication_Theory

Having B continuously return the current robot status information to A makes this communication behavior more aligned with our needs for controlling robot navigation.

Input 10 will accumulate all numbers from 1 to 10, and will iterate through all numbers from 1 to 10, performing the accumulation. The accumulation is time-consuming; assuming each accumulation takes one second.
Then, to make the program's running status visible, an INFO message is sent with each accumulation to indicate the current progress.

You can cancel the task while it is in progress.
First, the client sends the target data to the server.
Second, the server evaluates the target data and feeds the evaluation result back to the client (whether the target can be achieved).
Third, the client sends the final confirmed target data to the server.
Step 4: The server continuously feeds back the execution process data to the client.
Step five: After completion, the server returns 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 continuous feedback data.




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








ros2 interface show base_interfaces_demo/action/Progress
Action Communication_Experiment 1_Server Implementation (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; we only need to set the action, which is our action interface type.
The first parameter is node; use the this pointer within the class.
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 the callback function that generates continuous feedback after receiving the target value.
The sixth and seventh parameters have default values, so ignore them for now.
The return value is an action smart pointer.






Goal_callback parsing:
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, which encapsulates three constants underneath.
The first one is to receive and execute immediately.
The second is to receive and defer execution.
The third is rejection.





The error is caused by not adding a placeholder.


#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 + parameters
-f is for continuous feedback, meaning it can retrieve continuous feedback.

Send target value of 10.
Then we set an ID for our client. Since multiple clients may access this server, we need to assign a unique ID to each client.
Then the result is 0.
Because our program hasn't written anything yet.

The uuid is the client ID. Since it is not used here, use (void)uuid, because the compiler may issue a warning if it is not used.





Our task can be canceled normally, so simply returning accept is sufficient. Different actions can be performed within the function based on the specific requirements of each task.


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





Generate a continuous feedback API that requires parameters. The parameter passed in is the Feedback object from Progress.

get_goal() can retrieve the target value.


Because this is a time-consuming operation, in order to see the effect, we set a sleep each time we loop.

1.0 refers to 1 Hz, meaning it executes once every second.

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

The API to generate the final result requires parameters, and the parameter passed in is the Result object from Progress.

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










Bug: When we terminate the client, the server does not stop running. The server must keep executing until the program ends.

Bug Resolution Approach:
After receiving a cancellation request, my main logic is interrupted, meaning execute_callback is terminated.

This function returns a boolean value. It returns true if a cancellation request is received, otherwise it returns false.

After we cancel, we can still report the final result back to the client using the canceled function.



Move the definition of result to the front.



#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;
}
Action Communication_Experiment 1_Client Implementation (C++)

This red line is already encapsulated for us in action_client, so there's no 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 input parameter is node.
The second input parameter is the topic name, a string.
The third input parameter and the fourth input parameter both have default values.
The return value is a client-side smart pointer.




Need to split one step into two steps.

Now call the send_goal function again.


async_send_goal sends the target value asynchronously.

The first input parameter is the target value in our interface file.
The second input parameter is the target sending options object, through which we can set certain callback functions that need to be handled after the target is sent.
The return value is




For now, just define the functions casually, with void as both the return type and entry parameters, just to prevent the program from throwing errors.



Got it.
The GoalResponseCallback return type is void, and its entry parameter is goalhandle. In the diagram above, this goalhandle is a ClientGoalHandle, which belongs to the rclcpp_action namespace.





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






Send a value to the server. The server must first retrieve the target value, determine whether it can be accepted or rejected, and then respond with the processing result to the client.
If this target value is processable, then the goal_handle will contain content.
If it cannot be processed, then goal_handle is a nullptr.




Now processing the feedback data:

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



You need to use two % signs to escape a %, as shown in the example 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.


Callback functions may experience data loss. This is normal behavior.
Final response.
The final status of this result is uncertain; the task may have been canceled, terminated, or completed normally.
Therefore, we need to determine the status based on the status code.

The first one was forcibly terminated.
The second is to cancel.
The third one is success.
The fourth one 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, we cancel the client, but the server is still running. The ctrl+c here only terminates our client, rather than instructing it to send a cancel request command. We need to capture our keyboard input in order to send the cancel request command.
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(), "无法发送终止请求...");
}
Parameter Service_Theory and API Introduction (C++)


Of course, there are special cases, such as setting parameters to private.


Other communication methods require you to create your own interface files, but parameter services do not. ROS2 has already encapsulated the API, so we only need to call the API.



Just to demonstrate the API, first create a parameter package to show the API, without creating the client and server for now.



rclcpp::parameter object (key, value);







This function assigns a value to the parameter, with 18 overloads for various types.
The term "empty" means not assigning a value, so there are only keys, no values.
Parameter Service_Experiment 1_Server (C++)


Here's the difference: a second parameter is passed in Node, which is specifically used to allow parameter deletion. undeclared removes the declaration.





Only query and modification interface APIs are provided, without APIs for creation or deletion. This is a security consideration in ROS2.




#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;
}







Chain programming.
An ordinary node can act as a parameter server without needing to create a separate 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.

Use ros2 param get /node_name parameter_key to view the value of a parameter.

You can query the value of a parameter by its key.

Functions with plural forms can obtain some parameter objects through containers composed of keys.

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







The parameter object needs to be passed in.

We just overwrite the old value.


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





Parameters declared in this way cannot be deleted; only undeclared but set parameters can be removed.


#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;
}
Parameter Service Experiment 1 - Client (C++)










The first entry parameter is the client node object.
The second entry parameter is the name of the server node to connect to.

If connected within 1 second, return true; if the connection times out after 1 second, return false.


#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 this node name.




Use advanced for






Entry parameter: the container for the parameter object.


We can not only modify values, but also create new parameters, provided that the server has called undeclared...

