中间件面向对象基础-话题之RCLCPP实现
本节我们学习使用ROS2的RCLCPP客户端库来实现话题通信。
RCLCPP为Node类提供了丰富的API接口,其中就包括创建话题发布者和创建话题订阅者。
1.创建节点
本节我们将创建一个控制节点和一个被控节点。
控制节点创建一个话题发布者,发布控制命令(command)话题,接口类型为字符串(string),控制接点通过发布者发布控制命令(前进、后退、左转、右转、停止)。
被控节点创建一个订阅者,订阅控制命令,收到控制命令后根据命令内容打印对应速度出来。
cd d2lros2/
mkdir -p chapt3/chapt3_ws/src
cd chapt3/chapt3_ws/src
ros2 pkg create example_topic_rclcpp --build-type ament_cmake --dependencies rclcpp
touch example_topic_rclcpp/src/topic_publisher_01.cpp

2.编写发布者
消息类型(msgT)、话题名称(topic_name)和 服务指令(qos)。
2.2 导入消息接口:
ament_cmake类型功能包导入消息接口分为三步:
1.在CMakeLists.txt中导入,具体是先find_packages再ament_target_dependencies。
2.在packages.xml中导入,具体是添加depend标签并将消息接口写入。
3.在代码中导入,C++中是#include"消息功能包/xxx/xxx.hpp"。
CMakeLists.txt
2.3 创建发布者
根据ROS2的RCLCPPAPI文档可以看出,我们需要提供消息接口、话题名称和服务质量Qos。
消息接口上面我们已经导入了,是std_msgs/msg/string.h。
话题名称(topic_name),我们就用control_command。
Qos,Qos支持直接指定一个数字,这个数字对应的是KeepLast队列长度。一般设置成10,即如果一次性有100条消息,默认保留最新的10个,其余的都扔掉。
代码分析:
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
// rclcpp/rclcpp.hpp:ROS 2 C++ 客户端库的核心头文件,提供节点、发布者、订阅者等基础功能。
// std_msgs/msg/string.hpp:ROS 2 标准消息库中的字符串消息类型,用于发布文本数据。
class TopicPublisher01 : public rclcpp::Node
//TopicPublisher01:自定义节点类,继承自rclcpp::Node。
//rclcpp::Node:ROS 2 中所有 C++ 节点的基类,提供节点初始化和通信功能。
{
public:
// 构造函数,有一个参数为节点名称
TopicPublisher01(std::string name) : Node(name)
// 参数:接收节点名称name,并通过初始化列表传递给基类rclcpp::Node。
// 基类初始化:Node(name)调用父类构造函数,设置节点名称。
{
RCLCPP_INFO(this->get_logger(), "大家好,我是%s.",name.c_str());
// RCLCPP_INFO:ROS 2 的日志宏,用于输出信息级日志
// this->get_logger():获取节点的日志记录器。
// 创建发布者
command_publisher_ = this->create_publisher<std_msgs::msg::String>("command", 10);
//create_publisher:Node 类的方法,用于创建发布者。
//模板参数:std_msgs::msg::String指定发布的消息类型。
//参数 1:"command"为发布的主题名称。
//参数 2:10为消息队列大小,用于缓存未发送的消息。
}
//先声名节点
private:
// 声明节点
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;
//command_publisher_:共享指针类型的发布者对象,用于发布String类型的消息。
//SharedPtr:C++ 智能指针,自动管理对象生命周期,避免内存泄漏。
};
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
/*创建对应节点的共享指针对象*/
auto node = std::make_shared<TopicPublisher01>("topic_publisher_01");
/* 运行节点,并检测退出信号*/
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
2.4 使用定时器定时发布数据
2.4.1 查看零时起API
虽然编写好了发布者,但是我们还没有发布数据,我们需要通过ROS2中的定时器来设置指定的周期调用回调函数,在回调函数里实现发布数据功能。
重点:create_wall_timer()

2.4.2 编写代码
一段代码解析:
timer_ = this->create_wall_timer(
std::chrono::milliseconds(500),
std::bind(&TopicPublisher01::timer_callback, this)
);
这行代码创建了一个挂钟定时器 (wall timer),它会:500 毫秒触发一次回调函数
回调函数名为timer_callback,属于当前类TopicPublisher01
将定时器对象的智能指针赋值给成员变量timer_
参数详解:
1.时间间隔参数
std::chrono::milliseconds(500)
使用 C++11 的chrono库精确表示时间间隔
这里设置为 500 毫秒,即定时器每 0.5 秒触发一次
2.回调函数绑定
std::bind(&TopicPublisher01::timer_callback, this)
std::bind是 C++ 标准库的函数适配器
&TopicPublisher01::timer_callback是类成员函数的指针
this表示将当前对象实例作为回调函数的上下文
整体效果:将timer_callback方法绑定到当前节点实例
4.回调函数签名
回调函数必须符合以下签名要求:返回类型为void,不接受任何参数,通常声明为私有成员函数
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class TopicPublisher01 : public rclcpp::Node
{
public:
// 构造函数,有一个参数为节点名称
TopicPublisher01(std::string name) : Node(name)
{
RCLCPP_INFO(this->get_logger(), "大家好,我是%s.", name.c_str());
// 创建发布者
command_publisher_ = this->create_publisher<std_msgs::msg::String>("command", 10);
// 创建定时器,500ms为周期,定时发布
timer_ = this->create_wall_timer(std::chrono::milliseconds(500), std::bind(&TopicPublisher01::timer_callback, this));
}
private:
void timer_callback()
{
// 创建消息
std_msgs::msg::String message;
message.data = "forward";
// 日志打印
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
// 发布消息
command_publisher_->publish(message);
}
// 声名定时器指针
rclcpp::TimerBase::SharedPtr timer_;
// 声明话题发布者指针
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;
};
2.4.3 代码讲解
定时器
定时器是ROS2中的另外一个常用功能,通过定时器可以实现按照一定周期调用某个函数以实现定时发布等逻辑。
定时器对应的类是 rclcpp::TimerBase,调用create_wall_timer将返回其共享指针。
创建定时器时传入了两个参数,这两个参数都利用了C++11的新特性。
代码如下:
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
// rclcpp/rclcpp.hpp:ROS 2 C++ 客户端库的核心头文件,提供节点、发布者、订阅者等基础功能。
// std_msgs/msg/string.hpp:ROS 2 标准消息库中的字符串消息类型,用于发布文本数据。
//创建发布者
//消息接口上面我们已经导入了,是std_msgs/msg/string.h。
//话题名称(topic_name),我们就用control_command。
//Qos,Qos支持直接指定一个数字,这个数字对应的是KeepLast队列长度。
//一般设置成10,即如果一次性有100条消息,默认保留最新的10个,其余的都扔掉。
class TopicPublisher01 : public rclcpp::Node
//TopicPublisher01:自定义节点类,继承自rclcpp::Node。
//rclcpp::Node:ROS 2 中所有 C++ 节点的基类,提供节点初始化和通信功能。
{
public:
// 构造函数,有一个参数为节点名称
TopicPublisher01(std::string name) : Node(name)
// 参数:接收节点名称name,并通过初始化列表传递给基类rclcpp::Node。
// 基类初始化:Node(name)调用父类构造函数,设置节点名称。
{
RCLCPP_INFO(this->get_logger(), "大家好,我是%s.",name.c_str());
// RCLCPP_INFO:ROS 2 的日志宏,用于输出信息级日志
// this->get_logger():获取节点的日志记录器。
// 创建发布者
command_publisher_ = this->create_publisher<std_msgs::msg::String>("command", 10);
//create_publisher:Node 类的方法,用于创建发布者。
//模板参数:std_msgs::msg::String指定发布的消息类型。
//参数 1:"command"为发布的主题名称。
//参数 2:10为消息队列大小,用于缓存未发送的消息。
// 创建定时器,500ms为周期,定时发布
// 每 500 毫秒触发一次回调函数
// 回调函数名为timer_callback,属于当前类TopicPublisher01
// 将定时器对象的智能指针赋值给成员变量timer_
timer_ = this->create_wall_timer(
std::chrono::milliseconds(500),
std::bind(&TopicPublisher01::timer_callback, this)
);
}
//先声名节点
private:
void timer_callback()
{
// 创建消息
std_msgs::msg::String message;
message.data = "forward";
// 日志打印
RCLCPP_INFO(this->get_logger(),"Publishing:%s",message.data.c_str());
// 发布消息
command_publisher_->publish(message);
}
// 声名定时器指针
rclcpp::TimerBase::SharedPtr timer_;
// 声明话题发布者指针
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;
//command_publisher_:共享指针类型的发布者对象,用于发布String类型的消息。
//SharedPtr:C++ 智能指针,自动管理对象生命周期,避免内存泄漏。
};
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
/*创建对应节点的共享指针对象*/
auto node = std::make_shared<TopicPublisher01>("topic_publisher_01");
/* 运行节点,并检测退出信号*/
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
3.编写订阅者
之所以我们可以用命令行看到数据,原因在于CLI创建了一个订阅者来订阅/command指令。接下来我们将要手动创建一个节点订阅并处理数据。
cd chapt3_ws/src/example_topic_rclcpp
touch src/topic_subscribe_01.cpp

浙公网安备 33010602011771号