【ROS 学习笔记】ROS1(Noetic) ROS2(Humble)话题创建全流程梳理
文章目录
- 一、ROS1创建话题流程
- **1. 准备工作**
- 2. 创建功能包(Package)
- 3. 编写发布者(Publisher)节点
- 3.1 C++版本发布者
- 3.2 Python版本发布者
- 4. 编写订阅者(Subscriber)节点
- 4.1 C++版本订阅者
- 4.2 Python版本订阅者
- 5. 配置编译规则(仅C++需要)
- 6. 编译工作空间
- 7. 测试话题通信
- 8. 验证话题状态
- 二、ROS2创建话题流程
- 1. 准备工作
- 2. 创建功能包(Package)
- 3. 编写发布者(Publisher)节点
- 3.1 C++版本发布者
- 3.2 Python版本发布者
- 4. 编写订阅者(Subscriber)节点
- 4.1 C++版本订阅者
- 4.2 Python版本订阅者
- 5. 配置编译规则
- 5.1 C++功能包配置(`CMakeLists.txt`)
- 5.2 Python功能包配置(`setup.py`)
- 6. 编译工作空间
- 7. 测试话题通信
- 8. 验证话题状态
- 总结 ROS 2与ROS 1的核心差异
一、ROS1创建话题流程
在ROS(Robot Operating System)中,话题(Topic)是节点(Node)之间基于发布-订阅(Publish-Subscribe)模式进行通信的核心机制。创建一个话题的完整步骤如下:
1. 准备工作
- 确保已安装ROS(以ROS Noetic为例,其他版本步骤类似)。
- 创建并初始化ROS工作空间(若已存在可跳过):
# 创建工作空间目录 mkdir -p ~/catkin_ws/src cd ~/catkin_ws # 初始化工作空间 catkin_make # 加载工作空间环境变量(每次新终端需执行,或添加到~/.bashrc) source devel/setup.bash
2. 创建功能包(Package)
ROS的代码需组织在功能包中,功能包需依赖至少一个ROS核心库(如roscpp
或rospy
)和消息类型库(如std_msgs
)。
cd ~/catkin_ws/src
# 创建功能包(格式:catkin_create_pkg 包名 依赖1 依赖2 ...)
catkin_create_pkg my_topic_pkg roscpp rospy std_msgs
my_topic_pkg
:自定义功能包名称。roscpp
/rospy
:分别为C++/Python的ROS库依赖。std_msgs
:标准消息类型库(提供String、Int32等基础消息)。
3. 编写发布者(Publisher)节点
发布者节点负责向话题发送消息。以下分别提供C++ 和Python版本示例(以发送std_msgs/String
类型消息为例)。
3.1 C++版本发布者
在my_topic_pkg/src/
目录下创建talker.cpp
:
#include "ros/ros.h"
#include "std_msgs/String.h"int main(int argc, char **argv) {// 初始化节点,节点名称为"talker_node"(需唯一)ros::init(argc, argv, "talker_node");// 创建节点句柄(管理节点资源)ros::NodeHandle n;// 创建发布者对象:发布到话题"chatter",消息类型为std_msgs::String,队列长度10ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 10);// 设置循环频率(10Hz)ros::Rate loop_rate(10);int count = 0;while (ros::ok()) { // 当节点正常运行时循环std_msgs::String msg;std::stringstream ss;ss << "hello world " << count;msg.data = ss.str();// 打印日志(可选)ROS_INFO("发布消息: %s", msg.data.c_str());// 发布消息到话题chatter_pub.publish(msg);// 处理回调(若有)ros::spinOnce();// 按照循环频率休眠loop_rate.sleep();++count;}return 0;
}
核心代码讲解
这段代码是 ROS(机器人操作系统)中典型的消息发布逻辑,用于在节点运行时持续发送消息。以下是逐行解析:
-
while (ros::ok()) {
ros::ok()
检查 ROS 节点是否正常运行(未被关闭或中断)。- 当节点正常时返回
true
,循环持续执行;收到终止信号(如 Ctrl+C)时返回false
,循环退出。 - 作用:确保节点在活跃状态下持续工作。
-
std_msgs::String msg;
- 声明一个 ROS 标准消息类型
std_msgs::String
的对象msg
。 - 该消息类型包含一个字符串字段
data
,用于存储要发送的内容。
- 声明一个 ROS 标准消息类型
-
std::stringstream ss;
- 创建字符串流对象
ss
(C++ 标准库组件)。 - 用于高效拼接多个字符串或变量(避免频繁内存分配)。
- 创建字符串流对象
-
ss << "hello world " << count;
- 向字符串流写入内容:
- 固定字符串
"hello world "
- 变量
count
(通常是循环计数器,需在外部定义,如int count = 0;
)
- 固定字符串
- 示例输出:若
count=1
,则流内容为"hello world 1"
。
- 向字符串流写入内容:
-
msg.data = ss.str();
ss.str()
将字符串流内容转为std::string
。- 赋值给消息的
data
字段,完成消息组装。
3.2 Python版本发布者
在my_topic_pkg/scripts/
目录下创建talker.py
,并添加可执行权限:
#!/usr/bin/env python3
import rospy
from std_msgs.msg import Stringdef talker():# 初始化节点,节点名称为"talker_node"(需唯一)rospy.init_node('talker_node', anonymous=True) # anonymous=True确保节点名唯一# 创建发布者对象:发布到话题"chatter",消息类型为String,队列长度10pub = rospy.Publisher('chatter', String, queue_size=10)# 设置循环频率(10Hz)rate = rospy.Rate(10)count = 0while not rospy.is_shutdown(): # 当节点未关闭时循环hello_str = "hello world %d" % count# 打印日志(可选)rospy.loginfo("发布消息: %s", hello_str)# 发布消息到话题pub.publish(hello_str)# 按照循环频率休眠rate.sleep()count += 1if __name__ == '__main__':try:talker()except rospy.ROSInterruptException:pass
添加可执行权限:
chmod +x ~/catkin_ws/src/my_topic_pkg/scripts/talker.py
4. 编写订阅者(Subscriber)节点
订阅者节点负责从话题接收消息,并定义消息处理逻辑(回调函数)。
4.1 C++版本订阅者
在my_topic_pkg/src/
目录下创建listener.cpp
:
#include "ros/ros.h"
#include "std_msgs/String.h"// 回调函数:收到消息时自动调用
void chatterCallback(const std_msgs::String::ConstPtr& msg) {ROS_INFO("收到消息: %s", msg->data.c_str());
}int main(int argc, char **argv) {// 初始化节点,节点名称为"listener_node"ros::init(argc, argv, "listener_node");// 创建节点句柄ros::NodeHandle n;// 创建订阅者对象:订阅话题"chatter",队列长度10,收到消息时调用chatterCallbackros::Subscriber sub = n.subscribe("chatter", 10, chatterCallback);// 进入循环,等待回调函数触发(阻塞当前线程)ros::spin();return 0;
}
4.2 Python版本订阅者
在my_topic_pkg/scripts/
目录下创建listener.py
,并添加可执行权限:
#!/usr/bin/env python3
import rospy
from std_msgs.msg import String# 回调函数:收到消息时自动调用
def callback(data):rospy.loginfo("收到消息: %s", data.data)def listener():# 初始化节点,节点名称为"listener_node"rospy.init_node('listener_node', anonymous=True)# 创建订阅者对象:订阅话题"chatter",队列长度10,收到消息时调用callbackrospy.Subscriber("chatter", String, callback)# 进入循环,等待回调函数触发(阻塞当前线程)rospy.spin()if __name__ == '__main__':listener()
添加可执行权限:
chmod +x ~/catkin_ws/src/my_topic_pkg/scripts/listener.py
5. 配置编译规则(仅C++需要)
Python脚本无需编译,但C++代码需通过CMakeLists.txt
配置编译规则。
编辑my_topic_pkg/CMakeLists.txt
,在末尾添加:
# 找到依赖包
find_package(catkin REQUIRED COMPONENTSroscpprospystd_msgs
)# 声明catkin依赖
catkin_package(CATKIN_DEPENDS roscpp rospy std_msgs
)# 包含头文件目录
include_directories(${catkin_INCLUDE_DIRS}
)# 编译发布者节点
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
# 安装发布者节点到devel/lib/my_topic_pkg
install(TARGETS talkerRUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)# 编译订阅者节点
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
install(TARGETS listenerRUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
6. 编译工作空间
cd ~/catkin_ws
catkin_make # 编译所有功能包
source devel/setup.bash # 刷新环境变量
7. 测试话题通信
打开3个终端,分别执行以下命令:
-
启动ROS核心服务(roscore):
roscore
-
运行发布者节点:
- C++版本:
rosrun my_topic_pkg talker
- Python版本:
rosrun my_topic_pkg talker.py
- C++版本:
-
运行订阅者节点:
- C++版本:
rosrun my_topic_pkg listener
- Python版本:
rosrun my_topic_pkg listener.py
- C++版本:
8. 验证话题状态
使用ROS工具查看话题信息:
rostopic list # 列出所有活跃话题(应包含"/chatter")
rostopic echo /chatter # 实时打印话题"/chatter"的消息
rostopic info /chatter # 查看话题的消息类型和发布/订阅者信息
创建ROS1话题的核心流程是:创建工作空间与功能包 → 编写发布者/订阅者节点 → 配置编译(C++)→ 编译并测试。关键是确保发布者和订阅者使用相同的话题名称和消息类型,否则无法通信。
二、ROS2创建话题流程
在ROS 2(以Humble版本为例)中,话题(Topic)的创建逻辑与ROS 1类似,但工具链和API有较大变化。以下是ROS 2中创建话题通信的完整步骤:
1. 准备工作
- 确保已安装ROS 2 Humble(参考官方安装指南)。
- 创建并初始化ROS 2工作空间(若已存在可跳过):
# 创建工作空间目录 mkdir -p ~/ros2_ws/src cd ~/ros2_ws
2. 创建功能包(Package)
ROS 2功能包通过ros2 pkg create
创建,需指定构建类型(C++用ament_cmake
,Python用ament_python
)和依赖。
cd ~/ros2_ws/src
# 创建C++功能包(支持C++节点)
ros2 pkg create my_topic_pkg --build-type ament_cmake --dependencies rclcpp std_msgs# 或创建Python功能包(支持Python节点)
ros2 pkg create my_topic_pkg --build-type ament_python --dependencies rclpy std_msgs
my_topic_pkg
:自定义功能包名称。rclcpp
/rclpy
:ROS 2的C++/Python客户端库。std_msgs
:标准消息类型库(提供String、Int32等基础消息)。
3. 编写发布者(Publisher)节点
发布者节点向话题发送消息,以下分别提供C++ 和Python版本示例(消息类型为std_msgs/String
)。
3.1 C++版本发布者
在my_topic_pkg/src/
目录下创建talker.cpp
:
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"using namespace std::chrono_literals;// 定义发布者节点类(继承自rclcpp::Node)
class TalkerNode : public rclcpp::Node {
public:TalkerNode() : Node("talker_node"), count_(0) {// 创建发布者:话题名为"chatter",消息类型String,队列长度10publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);// 创建定时器,每500ms触发一次回调(即2Hz频率发送消息)timer_ = this->create_wall_timer(500ms, std::bind(&TalkerNode::timer_callback, this));RCLCPP_INFO(this->get_logger(), "发布者节点已启动");}private:// 定时器回调函数:生成并发布消息void timer_callback() {auto msg = std_msgs::msg::String();msg.data = "hello world: " + std::to_string(count_++);RCLCPP_INFO(this->get_logger(), "发布: '%s'", msg.data.c_str());publisher_->publish(msg);}rclcpp::TimerBase::SharedPtr timer_; // 定时器rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; // 发布者size_t count_; // 计数器
};int main(int argc, char * argv[]) {// 初始化ROS 2rclcpp::init(argc, argv);// 运行节点(阻塞当前线程)rclcpp::spin(std::make_shared<TalkerNode>());// 退出时清理rclcpp::shutdown();return 0;
}
3.2 Python版本发布者
在my_topic_pkg/my_topic_pkg/
目录下创建talker.py
:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String# 定义发布者节点类(继承自Node)
class TalkerNode(Node):def __init__(self):super().__init__("talker_node") # 节点名称为"talker_node"# 创建发布者:话题名为"chatter",消息类型String,队列长度10self.publisher_ = self.create_publisher(String, "chatter", 10)# 创建定时器,每0.5秒触发一次回调timer_period = 0.5 # 单位:秒self.timer = self.create_timer(timer_period, self.timer_callback)self.count = 0self.get_logger().info("发布者节点已启动")# 定时器回调函数:生成并发布消息def timer_callback(self):msg = String()msg.data = f"hello world: {self.count}"self.publisher_.publish(msg)self.get_logger().info(f"发布: '{msg.data}'")self.count += 1def main(args=None):# 初始化ROS 2rclpy.init(args=args)# 创建并运行节点talker = TalkerNode()rclpy.spin(talker) # 阻塞当前线程,处理节点回调# 退出时清理talker.destroy_node()rclpy.shutdown()if __name__ == "__main__":main()
4. 编写订阅者(Subscriber)节点
订阅者节点从话题接收消息,并通过回调函数处理消息。
4.1 C++版本订阅者
在my_topic_pkg/src/
目录下创建listener.cpp
:
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"// 定义订阅者节点类
class ListenerNode : public rclcpp::Node {
public:ListenerNode() : Node("listener_node") {// 创建订阅者:话题名为"chatter",队列长度10,收到消息时调用回调函数subscription_ = this->create_subscription<std_msgs::msg::String>("chatter", // 话题名称10, // 消息队列长度std::bind(&ListenerNode::topic_callback, this, std::placeholders::_1) // 回调函数绑定);RCLCPP_INFO(this->get_logger(), "订阅者节点已启动"); // 日志输出}private:// 话题回调函数:处理收到的消息void topic_callback(const std_msgs::msg::String & msg) const {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<ListenerNode>()); // 运行节点rclcpp::shutdown();return 0;
}
核心代码解释:
create_subscription
方法:用于创建订阅者。- 模板参数:
<std_msgs::msg::String>
指定订阅的消息类型为字符串。 - 参数解释:
"chatter"
:订阅的话题名称。节点会监听名为chatter
的话题。10
:消息队列长度。当消息到达速度超过处理速度时,最多缓存10条消息,避免丢失。std::bind(...)
:绑定回调函数。std::bind
将成员函数topic_callback
绑定到当前对象(this
),std::placeholders::_1
表示回调函数的第一个参数(即收到的消息)。
- 模板参数:
- 日志输出:
RCLCPP_INFO
是ROS 2的日志宏,用于输出信息级日志。this->get_logger()
获取节点日志器,输出"订阅者节点已启动"
表示节点初始化成功。
void topic_callback(const std_msgs::msg::String & msg) const {RCLCPP_INFO(this->get_logger(), "收到: '%s'", msg.data.c_str()); // 处理消息
}
- 函数作用:当话题
chatter
有新消息时,ROS 2自动调用此回调函数。 - 参数:
const std_msgs::msg::String & msg
是常量引用,表示接收到的消息。使用引用避免复制开销。 - 消息处理:
msg.data.c_str()
提取消息中的字符串数据(msg
是std_msgs::msg::String
类型,其data
成员为std::string
)。RCLCPP_INFO
打印日志,格式化为"收到: '消息内容'"
。
4.2 Python版本订阅者
在my_topic_pkg/my_topic_pkg/
目录下创建listener.py
:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String# 定义订阅者节点类
class ListenerNode(Node):def __init__(self):super().__init__("listener_node") # 节点名称为"listener_node"# 创建订阅者:话题名为"chatter",队列长度10,收到消息时调用回调self.subscription = self.create_subscription(String, "chatter", self.listener_callback, 10)self.subscription # 防止未使用变量警告self.get_logger().info("订阅者节点已启动")# 话题回调函数:处理收到的消息def listener_callback(self, msg):self.get_logger().info(f"收到: '{msg.data}'")def main(args=None):rclpy.init(args=args)listener = ListenerNode()rclpy.spin(listener) # 运行节点listener.destroy_node()rclpy.shutdown()if __name__ == "__main__":main()
5. 配置编译规则
5.1 C++功能包配置(CMakeLists.txt
)
编辑my_topic_pkg/CMakeLists.txt
,在ament_package()
前添加:
# 编译发布者节点
add_executable(talker src/talker.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
install(TARGETStalkerDESTINATION lib/${PROJECT_NAME})# 编译订阅者节点
add_executable(listener src/listener.cpp)
ament_target_dependencies(listener rclcpp std_msgs)
install(TARGETSlistenerDESTINATION lib/${PROJECT_NAME})
5.2 Python功能包配置(setup.py
)
编辑my_topic_pkg/setup.py
,在entry_points
中添加节点入口:
entry_points={'console_scripts': ['talker = my_topic_pkg.talker:main', # 发布者节点'listener = my_topic_pkg.listener:main' # 订阅者节点],
},
6. 编译工作空间
cd ~/ros2_ws
colcon build # 编译所有功能包(首次编译耗时较长)
source install/setup.bash # 加载工作空间环境变量(每次新终端需执行)
7. 测试话题通信
打开3个终端,分别执行以下命令(每个终端需先source ~/ros2_ws/install/setup.bash
):
-
运行发布者节点:
- C++版本:
ros2 run my_topic_pkg talker
- Python版本:
ros2 run my_topic_pkg talker
- C++版本:
-
运行订阅者节点:
- C++版本:
ros2 run my_topic_pkg listener
- Python版本:
ros2 run my_topic_pkg listener
- C++版本:
8. 验证话题状态
使用ROS 2命令行工具查看话题信息:
ros2 topic list # 列出所有活跃话题(应包含"/chatter")
ros2 topic echo /chatter # 实时打印话题消息
ros2 topic info /chatter # 查看话题的消息类型和节点信息
ros2 interface show std_msgs/msg/String # 查看消息结构
通过以上步骤,即可在ROS 2 Humble中实现基于话题的节点通信。核心是确保发布者和订阅者使用相同的话题名称和消息类型。
总结 ROS 2与ROS 1的核心差异
- 工具链:ROS 2使用
colcon
编译,替代ROS 1的catkin_make
。 - 节点API:ROS 2节点通过类继承(
rclcpp::Node
/Node
)实现,更面向对象。 - 启动方式:ROS 2无需单独启动
roscore
,节点启动时自动连接到ROS 2系统。 - 消息处理:ROS 2默认支持DDS中间件,通信更可靠,支持分布式系统。