公司动态

5、ROS2——话题Topic

📅 2026/8/25 13:48:03
5、ROS2——话题Topic
一、话题的基本介绍在 ROS 2 中话题Topic是节点之间进行异步、单向、多对多数据通信的命名通道。它基于发布/订阅Publish/Subscribe模式发布者Publisher向指定话题发送消息的节点。订阅者Subscriber从指定话题接收消息的节点。话题是 ROS 2 通信架构中最基本、最常用的机制之一用于传输连续的数据流例如传感器数据、机器人状态、控制指令等。形象比喻话题就像一个“广播电台”发布者相当于广播电台订阅者相当于收音机。电台发布者不断广播节目消息所有调谐到该频率话题名称的收音机订阅者都能同时收到内容而电台并不知道谁在收听。机器人系统通常由大量功能模块组成例如传感器驱动、感知、定位、规划、控制、执行器等。这些模块需要频繁地交换数据而且往往是持续的、单向的数据流。ROS 2 话题正是为了满足这种通信需求而设计的核心机制。话题的基本特性特性说明异步通信发布者和订阅者不需要同时在线它们通过中间件自动发现和连接单向数据流数据只从发布者流向订阅者没有请求/响应机制如需响应请使用服务或动作多对多一个话题可以有多个发布者和多个订阅者互不干扰消息类型固定每个话题必须指定一种消息类型所有参与者必须使用相同类型QoS 可配置服务质量策略可以控制可靠性、历史记录、队列深度等命名空间支持话题名称可以带有命名空间方便组织大型系统二、消息Message每个话题都承载一种特定类型的消息。消息是 ROS 2 中定义的数据结构由字段组成字段可以是基本类型整型、浮点、布尔、字符串等或嵌套的其他消息类型。常用消息类型std_msgs/msg/String字符串std_msgs/msg/Int3232 位整数sensor_msgs/msg/LaserScan激光雷达扫描数据sensor_msgs/msg/Image图像数据geometry_msgs/msg/Twist速度指令线速度角速度nav_msgs/msg/Odometry里程计信息当然消息也可以自定义在之后的文章中我会一一的介绍到。三、Python文件的发布者创建一个Python文件我这里命名为robot_news_station.py#!/usr/bin/env python3 import rclpy from rclpy.node import Node from example_interfaces.msg import String 这是一个简单的 ROS 2 节点示例使用 Python 编写。 该节点会在启动时打印一条欢迎信息并每秒钟发布一次 Breaking news from the robot world! 消息到 robot_news 主题。 class RobotNewsStation(Node): def __init__(self): super().__init__(robot_news_station) self.robot_nameRobot1 self.publisher_ self.create_publisher(String, robot_news, 10) self.timer self.create_timer(0.5, self.publish_news) self.get_logger().info(Robot News Station is up and running!) def publish_news(self): msg String() msg.data fBreaking news from {self.robot_name}! self.publisher_.publish(msg) def main(argsNone): rclpy.init(argsargs) node RobotNewsStation() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ __main__: main()构造函数__init__def __init__(self): super().__init__(robot_news_station) self.robot_nameRobot1 self.publisher_ self.create_publisher(String, robot_news, 10) self.timer self.create_timer(0.5, self.publish_news) self.get_logger().info(Robot News Station is up and running!)初始化节点命名为robot_news_station。设置一个实例变量robot_name值为Robot1。创建发布者消息类型String话题名robot_news队列长度 10。创建定时器每 0.5 秒触发一次publish_news。打印启动日志。发布消息方法publish_newsdef publish_news(self): msg String() msg.data fBreaking news from {self.robot_name}! self.publisher_.publish(msg)创建一个String消息。设置消息内容使用 f-string 插入robot_name。通过发布者将消息发布到robot_news话题。四、Python文件的订阅者#!/usr/bin/env python3 import rclpy from rclpy.node import Node from example_interfaces.msg import String 这是一个简单的 ROS 2 节点示例使用 Python 编写。 该节点会在启动时打印一条欢迎信息并订阅 robot_news 主题的消息。 class SmartphoneNode(Node): def __init__(self): super().__init__(smartphone_node) self.subscription_ self.create_subscription(String, robot_news, self.robot_news_callback, 10) self.get_logger().info(Smartphone Node is up and running!) def robot_news_callback(self, msg: String): self.get_logger().info(msg.data) def main(argsNone): rclpy.init(argsargs) node SmartphoneNode() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ __main__: main()构造函数__init__def __init__(self): super().__init__(smartphone_node) self.subscription_ self.create_subscription(String, robot_news, self.robot_news_callback, 10) self.get_logger().info(Smartphone Node is up and running!)初始化节点命名为smartphone_node。创建订阅者消息类型String订阅话题robot_news回调函数为robot_news_callback队列长度 10。打印启动日志。订阅回调函数robot_news_callbackdef robot_news_callback(self, msg: String): self.get_logger().info(msg.data)当收到robot_news话题的消息时自动调用。将消息内容字符串通过日志打印出来。主函数maindef main(argsNone): rclpy.init(argsargs) node SmartphoneNode() rclpy.spin(node) node.destroy_node() rclpy.shutdown()初始化 rclpy。创建SmartphoneNode节点实例。进入事件循环spin持续等待并处理订阅回调。循环结束后销毁节点。关闭 rclpy。五、C文件的发布者#include chrono #include functional #include memory #include string #include rclcpp/rclcpp.hpp #include example_interfaces/msg/string.hpp using namespace std::chrono_literals; /* * 这是一个简单的 ROS 2 节点示例使用 C 编写。 * 该节点会在启动时打印一条欢迎信息并每 0.5 秒发布一次 * Breaking news from Robot1! 消息到 robot_news 主题。 */ class RobotNewsStation : public rclcpp::Node { public: RobotNewsStation() : Node(robot_news_station) { publisher_ this-create_publisherexample_interfaces::msg::String(robot_news, 10); timer_ this-create_wall_timer( 500ms, std::bind(RobotNewsStation::publish_news, this)); RCLCPP_INFO(this-get_logger(), Robot News Station is up and running!); } private: void publish_news() { auto msg example_interfaces::msg::String(); msg.data Breaking news from Robot1!; publisher_-publish(msg); } rclcpp::Publisherexample_interfaces::msg::String::SharedPtr publisher_; rclcpp::TimerBase::SharedPtr timer_; }; int main(int argc, char * argv[]) { rclcpp::init(argc, argv); rclcpp::spin(std::make_sharedRobotNewsStation()); rclcpp::shutdown(); return 0; }1. 包含头文件#include chrono #include functional #include memory #include string #include rclcpp/rclcpp.hpp #include example_interfaces/msg/string.hppchrono时间相关用于定时器周期。functional提供std::bind。memory智能指针。string字符串。rclcpp/rclcpp.hppROS 2 C 客户端库。example_interfaces/msg/string.hpp字符串消息类型。2. 使用命名空间using namespace std::chrono_literals;允许使用500ms这样的时间字面量。3. 类定义class RobotNewsStation : public rclcpp::Node自定义节点类继承自rclcpp::Node。4. 构造函数RobotNewsStation() : Node(robot_news_station) { publisher_ this-create_publisherexample_interfaces::msg::String(robot_news, 10); timer_ this-create_wall_timer( 500ms, std::bind(RobotNewsStation::publish_news, this)); RCLCPP_INFO(this-get_logger(), Robot News Station is up and running!); }初始化节点命名为robot_news_station。创建发布者类型String话题robot_news队列长度 10。创建定时器每 500 毫秒触发一次publish_news。打印启动日志。5. 发布方法publish_newsvoid publish_news() { auto msg example_interfaces::msg::String(); msg.data Breaking news from Robot1!; publisher_-publish(msg); }创建String消息。设置内容为固定字符串。通过发布者发布到话题。6. 私有成员rclcpp::Publisherexample_interfaces::msg::String::SharedPtr publisher_; rclcpp::TimerBase::SharedPtr timer_;publisher_发布者智能指针。timer_定时器智能指针用于保持定时器存活。7. 主函数mainint main(int argc, char * argv[]) { rclcpp::init(argc, argv); rclcpp::spin(std::make_sharedRobotNewsStation()); rclcpp::shutdown(); return 0; }初始化 rclcpp。创建RobotNewsStation节点实例用智能指针并进入事件循环spin。循环结束后关闭 rclcpp。六、C文件的订阅者#include functional #include memory #include rclcpp/rclcpp.hpp #include example_interfaces/msg/string.hpp /* * 这是一个简单的 ROS 2 节点示例使用 C 编写。 * 该节点会在启动时打印一条欢迎信息并订阅 robot_news 主题的消息。 */ class SmartphoneNode : public rclcpp::Node { public: SmartphoneNode() : Node(smartphone_node) { subscription_ this-create_subscriptionexample_interfaces::msg::String( robot_news, 10, std::bind(SmartphoneNode::robot_news_callback, this, std::placeholders::_1)); RCLCPP_INFO(this-get_logger(), Smartphone Node is up and running!); } private: void robot_news_callback(const example_interfaces::msg::String msg) { RCLCPP_INFO(this-get_logger(), %s, msg.data.c_str()); } rclcpp::Subscriptionexample_interfaces::msg::String::SharedPtr subscription_; }; int main(int argc, char * argv[]) { rclcpp::init(argc, argv); rclcpp::spin(std::make_sharedSmartphoneNode()); rclcpp::shutdown(); return 0; }1. 包含头文件#include functional #include memory #include rclcpp/rclcpp.hpp #include example_interfaces/msg/string.hppfunctional提供std::bind和std::placeholders。memory提供智能指针std::make_shared。rclcpp/rclcpp.hppROS 2 C 客户端库。example_interfaces/msg/string.hpp字符串消息类型。2. 类定义class SmartphoneNode : public rclcpp::Node自定义节点类继承自rclcpp::Node表示一个“智能手机”节点。3. 构造函数SmartphoneNode() : Node(smartphone_node) { subscription_ this-create_subscriptionexample_interfaces::msg::String( robot_news, 10, std::bind(SmartphoneNode::robot_news_callback, this, std::placeholders::_1)); RCLCPP_INFO(this-get_logger(), Smartphone Node is up and running!); }初始化节点命名为smartphone_node。创建订阅者消息类型String订阅话题robot_news队列长度 10。使用std::bind将成员函数robot_news_callback绑定为回调std::placeholders::_1表示接收消息参数。打印启动日志。4. 订阅回调函数void robot_news_callback(const example_interfaces::msg::String msg) { RCLCPP_INFO(this-get_logger(), %s, msg.data.c_str()); }当收到robot_news话题的消息时自动调用。将消息内容字符串通过日志打印出来。5. 私有成员rclcpp::Subscriptionexample_interfaces::msg::String::SharedPtr subscription_;subscription_订阅者智能指针用于保持订阅存活。6. 主函数mainint main(int argc, char * argv[]) { rclcpp::init(argc, argv); rclcpp::spin(std::make_sharedSmartphoneNode()); rclcpp::shutdown(); return 0; }初始化 rclcpp。创建SmartphoneNode节点实例用智能指针并进入事件循环spin。循环结束后关闭 rclcpp。七、命令行查看话题名称1. 列出所有活动话题ros2 topic list作用显示当前 ROS 2 图中所有正在通信的话题名称。常用选项-t同时显示每个话题的消息类型。-v显示详细信息发布者/订阅者数量、QoS 等。示例ros2 topic list -t ros2 topic list -v2. 查看话题详细信息ros2 topic info topic_name作用显示指定话题的消息类型、发布者数量、订阅者数量以及 QoS 设置。示例ros2 topic info /robot_news输出类似Type: example_interfaces/msg/String Publisher count: 1 Subscription count: 13. 查看话题的消息类型ros2 topic type topic_name作用只显示该话题使用的消息类型。示例ros2 topic type /robot_news输出example_interfaces/msg/String4. 实时查看话题内容ros2 topic echo topic_name作用在终端实时打印发布到该话题的每一条消息内容是调试时最常用的命令。示例ros2 topic echo /robot_news输出每秒一条textdata: Breaking news from Robot1! ---5. 查看话题发布频率ros2 topic hz topic_name作用统计并显示话题的发布频率Hz用于检查数据流是否正常。示例ros2 topic hz /robot_news输出average rate: 2.000 min: 0.500s max: 0.500s std dev: 0.00000s window: 26. 查看话题带宽ros2 topic bw topic_name作用统计并显示话题的带宽bytes/s用于评估数据传输量。示例ros2 topic bw /robot_news7. 根据消息类型查找话题ros2 topic find msg_type作用查找所有使用指定消息类型的话题例如找出所有发布String消息的话题。示例ros2 topic find example_interfaces/msg/String输出/robot_news8. 发布一条测试消息非查看但常用ros2 topic pub topic_name msg_type data作用向话题手动发布一条消息用于测试订阅者。示例ros2 topic pub /robot_news example_interfaces/msg/String data: Hello Test选项--once只发布一次后退出。--rate Hz以指定频率持续发布。9. 图形化查看话题和节点连接rqt_graph作用打开图形界面可视化展示节点、话题及其连接关系非常直观。八、话题名称重映射基本语法使用ros2 run或ros2 launch时可以通过--ros-args传递重映射参数ros2 run package executable --ros-args -r 原话题名:新话题名-r是--remap的缩写。原话题名是节点代码中使用的名称相对名或绝对名均可。新话题名是你希望实际使用的名称。示例假设有一个节点talker发布到话题chatter# 原样运行 ros2 run demo_nodes_cpp talker # 发布到 /chatter # 重映射发布到 /my_chatter ros2 run demo_nodes_cpp talker --ros-args -r chatter:my_chatter # 此时该节点实际发布到 /my_chatter另一个节点listener原本订阅chatter可以重映射为订阅my_chatterros2 run demo_nodes_cpp listener --ros-args -r chatter:my_chatter这样talker和listener就通过my_chatter连接起来了。使用绝对名称重映射的旧名称和新名称都可以使用绝对名称以/开头ros2 run demo_nodes_cpp talker --ros-args -r /chatter:/my_chatter如果旧名称是相对名称如chatter它会被解释为相对于节点命名空间。如果节点命名空间为/robot那么旧名称chatter实际上被解析为/robot/chatter。同时重映射多个话题可以连续使用多个-rros2 run my_pkg my_node --ros-args -r /input:/camera/image -r /output:/processed_image