本人在学习鱼香ROS2的过程中对代码还是有些不了解,于是把所写的代码进行解构分析其流程和逻辑,适合像我一样的初学者进行辅助学习。我们这里以c++代码为例。
一、发布者
1.cpp
发布者全部代码为:
#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);
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_;
};
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;
}
代码的框架为:
节点类TopicPublisher01:
继承于rclcpp::Node,表示这是一个 ROS 2 节点类
成员函数:
构造函数:初始化节点、创建定时器和发布者
timer_callback:定时触发,发布消息
成员变量:
command_publisher_:发布器,发布 std_msgs::msg::String 类型的消息
timer_:定时器,定时调用 timer_callback()
主函数:
调用 rclcpp::init 进行初始化。
创建 TopicPublisher01 节点对象。
使用 rclcpp::spin 让节点保持运行。
结束时调用 rclcpp::shutdown 关闭节点
整体流程为:
1.main函数中启动节点
2.调用构造函数创建TopicPublisher01 节点对象,名称为“topic_publisher_01”
3.创建 Publisher (发布者),名称为command,消息队列为10
4.创建 Timer (定时器),500ms进行触发
5.Timer 定时触发
6.程序终止
1.启动节点
rclcpp::init(argc, argv)
初始化 ROS 2 客户端库,解析命令行参数
2.创建节点
auto node = std::make_shared<TopicPublisher01>("topic_publisher_01");
使用make_shared来创建一个TopicPublisher01的节点,传入节点名称"topic_publisher_01',这里调用了TopicPublisher01类的构造函数TopicPublisher01(std::string name)
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);
timer_=this->create_wall_timer(std::chrono::milliseconds(500),std::bind(&TopicPublisher01::timer_callback,this));
}
进入构造函数后,使用RCLCPP_INFO函数,用于将日志消息输出到终端,我们就能在命令行中看到这一条消息
3.创建发布者
在这个api文档里面可以看到,该函数要传入消息类型T,话题名称topic_name,服务指令qos和其他选项。
在构造函数里面,创建了发布者
command_publisher_ = this->create_publisher<std_msgs::msg::String>("command", 10);
类里面成员变量定义了发布者
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;
发布者command_publisher的类型为智能指针SharedPtr,用以管理发布者的生命周期,<>内存放了发布的消息类型,这里为std_msgs::msg::String,后续我们还可以自己定义数据类型。
再回到我们创建发布者的函数,create_publisher<T>()函数是ROS2提供用于创建我们的发布者。
这里Topic的名称为"command",消息会发布到command这个话题中,队列大小为10,表示消息的缓冲区大小。
4.定时器创建
这里定时器创建函数需要我们传入回调周期period,回调函数callback和调用回调函数所在的回调组group,group默认为nullptr
在构造函数里面,创建了定时器,
timer_ = this->create_wall_timer(
std::chrono::milliseconds(500),
std::bind(&TopicPublisher01::timer_callback, this));
在类里面定义了定时器成员变量
rclcpp::TimerBase::SharedPtr timer_;
定时器timer_的类型为智能指针SharedPtr,用以管理定时器的生命周期
回到定时器的创建,create_wall_timer()函数用于创建定时器,
第一个参数std::chrono::milliseconds(500),设置了定时器触发周期为500毫秒
第二个参数,这里其实是要我们传入回调函数,std::bind(&TopicPublisher01::timer_callback, this)),这里使用bind函数,将对象指针this和回调函数timer_callback进行绑定,当对象创建的定时器触发后,会调用回调函数,这样设置好了500ms触发一次,进入回调函数。
5.Timer 定时触发
我们定义了私有方法,定时器回调
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);
}
进入回调函数后,创建了消息对象message,其类型为std_msgs::msg::String,设置消息的内容为"forward",使用RCLCPP_INFO函数,用于将日志消息输出到终端,将当前消息的内容"forward"输出到终端,使用c_str()将string转为和c兼容的char *类型,用于输出。
调用发布者的publishe()方法,将消息发布到command话题中,便于订阅者subscribe进行消息订阅。
6.程序执行并终止
rclcpp::spin(node)
让节点保持运行,监听事件并调用回调函数,用于触发timer_callback()
rclcpp::shutdown()
停止运行
根据这些,我们能总结出写一个发布节点的模板代码
#include "rclcpp/rclcpp.hpp" // ROS 2 核心客户端库
#include "std_msgs/msg/string.hpp" // 消息类型,根据需求修改
//我们用T代替消息类型
// 自定义发布者节点类
class PublisherNode : public rclcpp::Node
{
public:
// 构造函数,设置节点名称
PublisherNode(const std::string &node_name) : Node(node_name)
{
RCLCPP_INFO(this->get_logger(), "%s 节点已启动", node_name.c_str());
// 创建发布者
publisher_ = this->create_publisher<T>(topic_name_, 10);//主题名称和队列长度
// 创建定时器,定期调用回调函数
timer_ = this->create_wall_timer(
std::chrono::milliseconds(timer_interval_),
std::bind(&PublisherNode::timer_callback, this)
);
}
private:
// 定时器回调函数
void timer_callback()
{
auto message = T();
message.data = "Hello, ROS 2!"; // 修改这里的消息内容
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
// 成员变量
rclcpp::Publisher<T>::SharedPtr publisher_; // 发布者
rclcpp::TimerBase::SharedPtr timer_; // 定时器
// 可修改的参数
const std::string topic_name_ = "topic_name"; // 发布的主题名称
const int timer_interval_ = 500; // 定时器间隔 (单位:毫秒)
};
// 主函数
int main(int argc, char **argv)
{
rclcpp::init(argc, argv); // 初始化 ROS 2 客户端
auto node = std::make_shared<PublisherNode>("publisher_node"); // 创建节点对象
rclcpp::spin(node); // 运行节点,保持监听回调
rclcpp::shutdown(); // 关闭 ROS 2 客户端
return 0;
}
二、订阅者
1.cpp
订阅者的全部代码为:
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class TopicSubscribe01 :public rclcpp::Node
{
public:
TopicSubscribe01(std::string name):Node(name)
{
RCLCPP_INFO(this->get_logger(),"hello, im %s",name.c_str());
command_subscribe_=this->create_subscription<std_msgs::msg::String>("command",10,std::bind(&TopicSubscribe01::command_callback,this,std::placeholders::_1));
}
private:
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr command_subscribe_;
void command_callback(const std_msgs::msg::String::SharedPtr msg)
{
double speed =0.0f;
if(msg->data=="forward")
{
speed=0.2f;
}
RCLCPP_INFO(this->get_logger(),"cover [%s] command, speed %f",msg->data.c_str(),speed);
}
};
int main(int argc,char **argv)
{
rclcpp::init(argc,argv);
auto node =std::make_shared<TopicSubscribe01>("topic_subscribe_01");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
代码的框架为:
节点类 TopicSubscribe01:
继承自 rclcpp::Node,表示这是一个 ROS2订阅者节点类
成员函数:
构造函数:初始化节点并创建订阅者
command_callback:处理接收到的消息
成员变量:
command_subscribe_:订阅者,订阅 std_msgs::msg::String 类型的消息
主函数:
调用 rclcpp::init 进行初始化
创建 TopicSubscribe01 节点对象
使用 rclcpp::spin 让节点保持运行
结束时调用 rclcpp::shutdown 关闭节点
整体流程为:
1.main函数中启动节点
2.调用构造函数创建TopicSubscribe01节点对象,名称为“topic_subscribe_01”
3.创建Subscribe(订阅者),订阅话题为command,消息队列为10
4.等待并处理消息
5.消息到达,进入回调函数command_callback()处理消息
6.程序终止
1.启动节点
rclcpp::init(argc, argv)
初始化 ROS 2 客户端库,解析命令行参数
2.创建节点
auto node =std::make_shared<TopicSubscribe01>("topic_subscribe_01");
使用make_shared来创建一个TopicSubscribe01的节点,传入节点名称"topic_subscribe_01',这里调用了TopicSubscribe01类的构造函数TopicSubscribe01(std::string name)
3.订阅者创建
在这个api文档里面可以看到,该函数要传入消息类型T,话题名称topic_name,服务指令qos,回调函数callback,后面两个参数都有默认值。
在构造函数里面,创建了订阅者
command_subscribe_=this->create_subscription<std_msgs::msg::String>("command",10,std::bind(&TopicSubscribe01::command_callback,this,std::placeholders::_1));
类里面成员变量定义了订阅者
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr command_subscribe_;
发布者command_subscribe_的类型为智能指针SharedPtr,用以管理发布者的生命周期,<>内存放了发布的消息类型,这里为std_msgs::msg::String,后续我们还可以自己定义数据类型。
再回到我们创建订阅者的函数,create_subscription<T>()函数是ROS2提供用于创建我们的发布者。
这里订阅的Topic的名称为"command",消息会发布到command这个话题中,队列大小为10,表示消息的缓冲区大小,第三个参数,这里其实是要我们传入回调函数,std::bind(&TopicSubscribe01::command_callback,this,std::placeholders::_1),这里使用bind函数,将对象指针this和回调函数timer_callback进行绑定,std::placeholders::_1
是一个占位符,表示 command_callback
函数的第一个参数。
我们的command_callback参数为
void command_callback(const std_msgs::msg::String::SharedPtr msg)
因此这里的std::placeholders::_1代表msg,所以std::bind(&TopicSubscribe01::command_callback, this, std::placeholders::_1)
创建了一个可调用对象,它绑定了成员函数 command_callback
,并且在调用时会将 this
指针和一个参数(即接收到的消息)传递给它。
4.等待消息
rclcpp::spin(node);
这句话里面,我们进入事件循环,等待接收消息。
5.处理消息
接收到消息msg后,我们进入回调函数command_callback中,处理接受到的消息
void command_callback(const std_msgs::msg::String::SharedPtr msg)
{
double speed =0.0f;
if(msg->data=="forward")
{
speed=0.2f;
}
RCLCPP_INFO(this->get_logger(),"cover [%s] command, speed %f",msg->data.c_str(),speed);
}
command_callback
方法:这是处理收到消息的回调函数。它接受一个std_msgs::msg::String::SharedPtr
类型的消息指针msg,然后进行判断,如果接受的消息数据为"forward",则把speed设为0.2,使用RCLCPP_INFO打印日志到终端,打印内容为接受到的消息数据msg->data和speed,同样使用c_str()将string转为c的char*。
6.结束程序
rclcpp::shutdown()
停止运行
根据这些,我们能总结出写一个订阅节点的模板代码
#include "rclcpp/rclcpp.hpp" // ROS 2 核心客户端库
#include "std_msgs/msg/string.hpp" // 消息类型,根据需求修改
// 我们用T代替消息类型
// 自定义订阅者节点类
class SubscriberNode : public rclcpp::Node
{
public:
// 构造函数,设置节点名称
SubscriberNode(const std::string &node_name) : Node(node_name)
{
RCLCPP_INFO(this->get_logger(), "%s 节点已启动", node_name.c_str());
// 创建订阅者
subscription_ = this->create_subscription<T>(
topic_name_, 10, // 主题名称和队列长度
std::bind(&SubscriberNode::message_callback, this, std::placeholders::_1)
);
}
private:
// 回调函数:处理接收到的消息
void message_callback(const typename T::SharedPtr msg)
{
RCLCPP_INFO(this->get_logger(), "Received message: '%s'", msg->data.c_str());
// 这里可以添加自定义的消息处理逻辑
}
// 成员变量
rclcpp::Subscription<T>::SharedPtr subscription_; // 订阅者
// 可修改的参数
const std::string topic_name_ = "topic_name"; // 订阅的主题名称
};
// 主函数
int main(int argc, char **argv)
{
rclcpp::init(argc, argv); // 初始化 ROS 2 客户端
auto node = std::make_shared<SubscriberNode>("subscriber_node"); // 创建节点对象
rclcpp::spin(node); // 运行节点,保持监听回调
rclcpp::shutdown(); // 关闭 ROS 2 客户端
return 0;
}
三、CMakeLists的修改和package.xml的修改
1.package.xml中添加依赖
在package.xml中,添加对消息信息std_msgs包的依赖
2.cmakelists.txt
1.add_executable
add_executable(topic_publisher_01 src/topic_publisher_01.cpp)
add_executable(topic_subscribe_01 src/topic_subscribe_01.cpp)
添加这两行是为了将指定的 源代码文件 编译成可执行文件
topic_publisher_01 是基于 src/topic_publisher_01.cpp 构建的可执行文件。
topic_subscribe_01 是基于 src/topic_subscribe_01.cpp 构建的可执行文件。
2.ament_target_dependencies
ament_target_dependencies(topic_publisher_01 rclcpp std_msgs)
ament_target_dependencies(topic_subscribe_01 rclcpp std_msgs)
这两句是为可执行文件添加依赖项,确保正确链接到相应的库,这里将可执行文件正确地链接到 rclcpp
和 std_msgs
库
3.install
install(TARGETS
topic_publisher_01
DESTINATION lib/${PROJECT_NAME}
)
install(TARGETS
topic_subscribe_01
DESTINATION lib/${PROJECT_NAME}
)
这两句是为了将编译好的可执行文件安装到指定目录,以便在运行时可以找到它们,
install(TARGETS <target_name> DESTINATION <directory>)
<target_name>:要安装的目标(如可执行文件)
<directory>:目标被安装到的目录
这样做的目的是:确保构建和安装之后,可以通过 ROS 2 的命令找到这些可执行文件,使得程序能够通过 ros2 run example_topic_rclcpp topic_publisher_01 启动节点
标签:std,topic,代码,rclcpp,解读,callback,command,节点,ROS2 From: https://blog.csdn.net/fegxg/article/details/144310049