# 《ROS2智能机器人开发与实践》——ROS核心(节点、话题、服务、DDS通信协议等)
今天主要是分享一下ROS2的核心包括节点、话题、服务和DDS通信协议的内容
其中会对官方和本书的相关细节进行比对
本文章都以C++为例
一、节点
1、ROS2中的节点具有以下几个特点
- 每个节点是一个进程
- 每个节点都是一个可以独立运行的可执行文件
- 每个节点可使用不同的编程语言
- 每个节点可分布式运行在不同的主机中
- 每个节点需要唯一的名称
2、节点编程方法(C++)
a.C++程序编写

#include "rclcpp/rclcpp.hpp"
创建一个HelloWorld节点, 初始化时输出“hello world”日志
***/
class HelloWorldNode : public rclcpp::Node
{
public:
HelloWorldNode()
: Node("node_helloworld_class")
{
while(rclcpp::ok())
{
RCLCPP_INFO(this->get_logger(), "Hello World");
sleep(1);
}
}
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<HelloWorldNode>());
rclcpp::shutdown();
return 0;
}
** 可以看的出来ROS2对节点的编程推荐面向对象编程
的编程方法**
同时ROS2的编程难度更大、C++的语法更新、所以使用python也不失为一种方法
b.设置编译选项(编译接口)
find_package(rclcpp REQUIRED)
add_executable(node_helloworld_class src/node_helloworld_class.cpp)
ament_target_dependencies(node_helloworld_class rclcpp)
install(TARGETS
node_helloworld_class
DESTINATION lib/${PROJECT_NAME})
前三行相对于ROS1没什么太大区别,主要是多了最后这个
将编译生成的可执行文件拷贝到install安装空间的lib文件夹下
我们知道ROS2相对于ROS1的工作空间构建方式变了,其中多了install文件夹 所以要注意最后一行
c.编译运行
colcon build
ros2 run learning_node_cpp node_helloworld_class
ros2 node list
ros2 node info <node_name>
二、话题

1.rqt_graph
ros2 run turtlesim turtlesim_node
ros2 run turtlesim turtle_teleop_key
rqt_graph
我们将使用 rqt_graph
来可视化不断变化的节点和主题,以及它们之间的联系。

话题的特点:
话题通信的一个特性,就是异步通信
所谓异步,只要是指发布者发出数据后,并不知道订阅者什么时候可以收到。所以ROS2的后台会有一个轮询机制,当发现有数据进入消息队列时,就会触发回调函数
2.发布话题
@作者: 古月居(www.guyuehome.com)
@说明: ROS2话题示例-发布图像话题
***/
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
class PublisherNode : public rclcpp::Node
{
public:
PublisherNode()
: Node("topic_helloworld_pub")
{
publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&PublisherNode::timer_callback, this));
}
private:
void timer_callback()
{
auto msg = std_msgs::msg::String();
msg.data = "Hello World";
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", msg.data.c_str());
publisher_->publish(msg);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<PublisherNode>());
rclcpp::shutdown();
return 0;
}
都是面向对象编程
3.订阅话题
@作者: 古月居(www.guyuehome.com)
@说明: ROS2话题示例-订阅“Hello World”话题消息
***/
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;
class SubscriberNode : public rclcpp::Node
{
public:
SubscriberNode()
: Node("topic_helloworld_sub")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"chatter", 10, std::bind(&SubscriberNode::topic_callback, this, _1));
}
private:
void topic_callback(const std_msgs::msg::String & msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%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<SubscriberNode>());
rclcpp::shutdown();
return 0;
}
3.服务

服务可以实现类似你问我答的同步通信
效果
a.服务的特点
服务的特点:
b.客户端
@作者: 古月居(www.guyuehome.com)
@说明: ROS2服务示例-发送两个加数,请求加法器计算
***/
#include "rclcpp/rclcpp.hpp"
#include "learning_interface/srv/add_two_ints.hpp"
#include <chrono>
#include <cstdlib>
#include <memory>
using namespace std::chrono_literals;
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
if (argc != 3) {
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: service_adder_client X Y");
return 1;
}
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("service_adder_client");
rclcpp::Client<learning_interface::srv::AddTwoInts>::SharedPtr client =
node->create_client<learning_interface::srv::AddTwoInts>("add_two_ints");
auto request = std::make_shared<learning_interface::srv::AddTwoInts::Request>();
request->a = atoll(argv[1]);
request->b = atoll(argv[2]);
while (!client->wait_for_service(1s)) {
if (!rclcpp::ok()) {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
return 0;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
}
auto result = client->async_send_request(request);
if (rclcpp::spin_until_future_complete(node, result) ==
rclcpp::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum);
} else {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_two_ints");
}
rclcpp::shutdown();
return 0;
}
c. 服务端
@作者: 古月居(www.guyuehome.com)
@说明: ROS2服务示例-提供加法器的服务器处理功能
***/
#include "rclcpp/rclcpp.hpp"
#include "learning_interface/srv/add_two_ints.hpp"
#include <memory>
void adderServer(const std::shared_ptr<learning_interface::srv::AddTwoInts::Request> request,
std::shared_ptr<learning_interface::srv::AddTwoInts::Response> response)
{
response->sum = request->a + request->b;
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld",
request->a, request->b);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("service_adder_server");
rclcpp::Service<learning_interface::srv::AddTwoInts>::SharedPtr service =
node->create_service<learning_interface::srv::AddTwoInts>("add_two_ints", &adderServer);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");
rclcpp::spin(node);
rclcpp::shutdown();
}
d.命令行操作
ros2 service list
ros2 service type <service_name>
ros2 service call <service_name> <service_type> <service_data>
4.通信接口


同ROS1一样ROS2同样有的可自定义的通信接口,这些通信接口大大拓展了ROS的应用场景
具体的自定义接口的方法可以看官方文档或者
5.动作

动作是一种应用层的通信机制,其底层是基于话题和服务实现的
a. 服务端
@作者: 古月居(www.guyuehome.com)
@说明: ROS2动作示例-负责执行圆周运动动作的服务端
***/
#include <iostream>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "learning_interface/action/move_circle.hpp"
using namespace std;
class MoveCircleActionServer : public rclcpp::Node
{
public:
using CustomAction = learning_interface::action::MoveCircle;
using GoalHandle = rclcpp_action::ServerGoalHandle<CustomAction>;
explicit MoveCircleActionServer(const rclcpp::NodeOptions & action_server_options = rclcpp::NodeOptions())
: Node("action_move_server", action_server_options)
{
using namespace std::placeholders;
this->action_server_ = rclcpp_action::create_server<CustomAction>(
this->get_node_base_interface(),
this->get_node_clock_interface(),
this->get_node_logging_interface(),
this->get_node_waitables_interface(),
"move_circle",
std::bind(&MoveCircleActionServer::handle_goal, this, _1, _2),
std::bind(&MoveCircleActionServer::handle_cancel, this, _1),
std::bind(&MoveCircleActionServer::handle_accepted, this, _1));
}
private:
rclcpp_action::Server<CustomAction>::SharedPtr action_server_;
rclcpp_action::GoalResponse handle_goal(
const rclcpp_action::GoalUUID & uuid,
std::shared_ptr<const CustomAction::Goal> goal_request)
{
RCLCPP_INFO(this->get_logger(), "Server: Received goal request: %d", goal_request->enable);
(void)uuid;
if (goal_request->enable)
{
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
else
{
return rclcpp_action::GoalResponse::REJECT;
}
}
rclcpp_action::CancelResponse handle_cancel(
const std::shared_ptr<GoalHandle> goal_handle_canceled_)
{
RCLCPP_INFO(this->get_logger(), "Server: Received request to cancel action");
(void) goal_handle_canceled_;
return rclcpp_action::CancelResponse::ACCEPT;
}
void handle_accepted(const std::shared_ptr<GoalHandle> goal_handle_accepted_)
{
using namespace std::placeholders;
std::thread{std::bind(&MoveCircleActionServer::execute, this, _1), goal_handle_accepted_}.detach();
}
void execute(const std::shared_ptr<GoalHandle> goal_handle_)
{
const auto requested_goal = goal_handle_->get_goal();
auto feedback = std::make_shared<CustomAction::Feedback>();
auto result = std::make_shared<CustomAction::Result>();
RCLCPP_INFO(this->get_logger(), "Server: Executing goal");
rclcpp::Rate loop_rate(1);
for (int i = 0; (i < 361) && rclcpp::ok(); i=i+30)
{
if (goal_handle_->is_canceling())
{
result->finish = false;
goal_handle_->canceled(result);
RCLCPP_INFO(this->get_logger(), "Server: Goal canceled");
return;
}
feedback->state = i;
goal_handle_->publish_feedback(feedback);
RCLCPP_INFO(this->get_logger(), "Server: Publish feedback");
loop_rate.sleep();
}
if (rclcpp::ok())
{
result->finish = true;
goal_handle_->succeed(result);
RCLCPP_INFO(this->get_logger(), "Server: Goal succeeded");
}
}
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MoveCircleActionServer>());
rclcpp::shutdown();
return 0;
}
b. 客户端
@作者: 古月居(www.guyuehome.com)
@说明: ROS2动作示例-请求执行圆周运动动作的客户端
***/
#include <iostream>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "learning_interface/action/move_circle.hpp"
using namespace std;
class MoveCircleActionClient : public rclcpp::Node
{
public:
using CustomAction = learning_interface::action::MoveCircle;
using GoalHandle = rclcpp_action::ClientGoalHandle<CustomAction>;
explicit MoveCircleActionClient(const rclcpp::NodeOptions & node_options = rclcpp::NodeOptions())
: Node("action_move_client", node_options)
{
this->client_ptr_ = rclcpp_action::create_client<CustomAction>(
this->get_node_base_interface(),
this->get_node_graph_interface(),
this->get_node_logging_interface(),
this->get_node_waitables_interface(),
"move_circle");
}
void send_goal(bool enable)
{
if (!this->client_ptr_->wait_for_action_server(std::chrono::seconds(10)))
{
RCLCPP_ERROR(this->get_logger(), "Client: Action server not available after waiting");
rclcpp::shutdown();
return;
}
auto send_goal_options = rclcpp_action::Client<CustomAction>::SendGoalOptions();
using namespace std::placeholders;
send_goal_options.goal_response_callback =
std::bind(&MoveCircleActionClient::goal_response_callback, this, _1);
send_goal_options.feedback_callback =
std::bind(&MoveCircleActionClient::feedback_callback, this, _1, _2);
send_goal_options.result_callback =
std::bind(&MoveCircleActionClient::result_callback, this, _1);
auto goal_msg = CustomAction::Goal();
goal_msg.enable = enable;
RCLCPP_INFO(this->get_logger(), "Client: Sending goal");
this->client_ptr_->async_send_goal(goal_msg, send_goal_options);
}
private:
rclcpp_action::Client<CustomAction>::SharedPtr client_ptr_;
void goal_response_callback(GoalHandle::SharedPtr goal_message)
{
if (!goal_message)
{
RCLCPP_ERROR(this->get_logger(), "Client: Goal was rejected by server");
rclcpp::shutdown();
}
else
{
RCLCPP_INFO(this->get_logger(), "Client: Goal accepted by server, waiting for result");
}
}
void feedback_callback(
GoalHandle::SharedPtr,
const std::shared_ptr<const CustomAction::Feedback> feedback_message)
{
std::stringstream ss;
ss << "Client: Received feedback: "<< feedback_message->state;
RCLCPP_INFO(this->get_logger(), "%s", ss.str().c_str());
}
void result_callback(const GoalHandle::WrappedResult & result_message)
{
switch (result_message.code)
{
case rclcpp_action::ResultCode::SUCCEEDED:
break;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(this->get_logger(), "Client: Goal was aborted");
rclcpp::shutdown();
return;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_ERROR(this->get_logger(), "Client: Goal was canceled");
rclcpp::shutdown();
return;
default:
RCLCPP_ERROR(this->get_logger(), "Client: Unknown result code");
rclcpp::shutdown();
return;
}
RCLCPP_INFO(this->get_logger(), "Client: Result received: %s", (result_message.result->finish ? "true" : "false"));
rclcpp::shutdown();
}
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
auto action_client = std::make_shared<MoveCircleActionClient>();
action_client->send_goal(true);
rclcpp::spin(action_client);
rclcpp::shutdown();
return 0;
}
c. 命令行操作
$ ros2 action list
$ ros2 action info <action_name>
$ ros2 action send_goal <action_name> <action_type> <action_data>
6.参数
参数是ROS2模块化封装的重要功能,可以利用全局参数可视化显示以及动态调整
默认情况下,某一个参数被修改后,使用它的节点并不会立刻被同步,ROS2提供了动态配置的机制,需要在程序中进行设置,通过回调函数的方法,动态调整参数
关于参数,具体可以看https://docs.ros.org/en/jazzy/Concepts/Basic/About-Parameters.html#parameters
7.DDS

DS的全称是Data Distribution Service,也就是数据分发服务,2004年由对象管理组织OMG发布和维护,是一套专门为实时系统设计的数据分发/订阅标准,最早应用于美国海军, 解决舰船复杂网络环境中大量软件升级的兼容性问题,现在已经成为强制标准。
DDS强调以数据为中心,可以提供丰富的服务质量策略,以保障数据进行实时、高效、灵活地分发,可满足各种分布式实时通信应用需求。
在命令行中配置DDS
$ ros2 topic pub /chatter std_msgs/msg/Int32 "data: 42" --qos-reliability best_effort
$ ros2 topic echo /chatter --qos-reliability reliable
$ ros2 topic echo /chatter --qos-reliability best_effort
$ ros2 topic info /chatter --verbose
8.分布式通信
分布式通信是解决使用一台计算机算力不足的情况
有几个实用的例子,也是我自己在使用的:
一台电脑运行执行器节点另一台电脑读取数据并使用QT可视化数据
一台电脑运行节点 另一台使用rviz等等