系统版本:v0.0.3
最后更新:2026-04-07
内容范围:工作空间、功能包结构、常用指令、节点定义、通信机制(Topic/Service/Action)、QoS策略、Launch 启动
一、 工作空间 (Workspace) 目录结构
在一个典型的 ROS 2 工作空间(如 ~/dev_ws)下,通常包含以下四个核心目录:
- 📁
src/:源码目录。用于存储所有功能包(Package)的源代码。
- 📁
build/:编译目录。存储编译过程中产生的中间文件,该目录会为每一个功能包创建单独的子目录。
- 📁
install/:安装目录。用于存储编译后生成的可执行文件、库文件和共享脚本等。
- 📁
log/:日志目录。存储编译和运行时的日志信息。
二、 功能包 (Package) 内部结构
存放在 src/ 目录下的功能包主要分为 C++ 包和 Python 包两种,它们的内部结构有所不同。
1. C++ 功能包 (ament_cmake)
C++ 功能包基于 CMake 构建系统,标准目录和文件结构如下:
| 文件/目录 |
说明 |
package.xml |
功能包清单文件。涵盖包的元数据(版权所有者、版权年份及声明),以及该功能包所依赖的库、工具和资源 |
CMakeLists.txt |
CMake 构建脚本。用于配置编译规则,包括指定编译器、编译选项、要链接的库等 |
src/ |
C++ 源文件(.cpp)存放目录 |
include/ |
C++ 头文件(.hpp / .h)存放目录 |
msg/ |
自定义消息接口文件(.msg)存放目录 |
srv/ |
自定义服务接口文件(.srv)存放目录 |
action/ |
自定义动作接口文件(.action)存放目录 |
launch/ |
节点启动文件(.launch.py 等)存放目录 |
2. Python 功能包 (ament_python)
Python 功能包基于 Python 的标准包管理工具构建,标准目录和文件结构如下:
| 文件/目录 |
说明 |
package.xml |
功能包清单文件(作用同 C++ 包) |
setup.py |
Python 包特有。涵盖功能包的元数据、依赖关系以及安装脚本等,是构建 Python 包的核心 |
setup.cfg |
Python 打包的配置文件,用于指定安装路径等信息 |
resource/ |
资源文件存放目录(通常用于 ROS 2 识别该包的标识) |
test/ |
测试代码存放目录 |
<功能包同名目录>/ |
Python 源文件存放目录(例如包名叫 my_pkg,则会有一个 my_pkg/ 文件夹存放具体的 .py 代码) |
launch/ |
节点启动文件存放目录 |
三、 ROS 2 常用指令备忘录
1. 基础状态查看
1 2 3 4 5
| ros2 node list
ros2 topic list -v
|
2. 环境变量配置
1 2
| export RCUTILS_CONSOLE_OUTPUT_FORMAT="[{function_name}:{line_number}]:{message}"
|
3. 功能包管理与编译
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
ros2 pkg create --build-type <build-type> <package_name>
cd ~/dev_ws colcon build --packages-select <package_name>
cd ~/dev_ws rm -rf install/ build/ log/
|
四、 节点 (Node) 定义方式
节点是 ROS 2 中执行计算任务的基本单元。一个机器人系统由多个节点组成,每个节点负责一个具体的功能。
1. Python 节点(类继承式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
import rclpy from rclpy.node import Node import time
class HelloWorldNode(Node): """自定义节点类,继承 rclpy.node.Node"""
def __init__(self, name): super().__init__(name)
while rclpy.ok(): self.get_logger().info("Hello World") time.sleep(0.5)
def main(args=None): rclpy.init(args=args)
node = HelloWorldNode("node_helloworld_class")
rclpy.spin(node)
node.destroy_node() rclpy.shutdown()
if __name__ == "__main__": main()
|
说明:
#!/usr/bin/env python3 是 shebang 行,让脚本可以直接执行
rclpy.ok() 检查 ROS2 是否仍在运行(Ctrl+C 时返回 False)
- 实际项目中,循环打印推荐改用
self.create_timer() 而非 while + sleep
2. C++ 节点(类继承式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <unistd.h> #include "rclcpp/rclcpp.hpp"
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; }
|
说明:
rclcpp::ok() 检查 ROS2 是否仍在运行
RCLCPP_INFO 是 C++ 版本的日志宏,等价于 Python 的 get_logger().info()
this->get_logger() 注意是 -> 而非 .(指针访问成员)
- 实际项目中,循环打印推荐改用
this->create_wall_timer()
五、 通信机制
💡 提示:ROS 2 通信机制包括话题(Topic)、服务(Service)、动作(Action)三种主要方式,每种方式都基于 DDS(Data Distribution Service) 中间件实现。通信时的服务质量策略(QoS)对通信行为有重要影响。
0. QoS(服务质量策略)
ROS 2 基于 DDS 提供了灵活的服务质量策略,可根据不同场景配置可靠性、持久性、优先级等行为。
| QoS 策略 |
说明 |
常用场景 |
| 可靠性 (Reliability) |
RELIABLE(保证送达)或 BEST_EFFORT(只管发送) |
可靠传输用 RELIABLE,实时数据用 BEST_EFFORT |
| 持久性 (Durability) |
TRANSIENT_LOCAL(新订阅者收到历史数据)或 VOLATILE(不保留历史) |
需要接收历史数据时用 TRANSIENT_LOCAL |
| 历史 (History) |
KEEP_LAST(n)(保留最近 n 条)或 KEEP_ALL(保留全部) |
控制缓存大小 |
| 深度 (Depth) |
队列大小,配合 KEEP_LAST 使用 |
|
| 截止时间 (Deadline) |
预期发布频率 |
监测数据是否按时到达 |
| 优先级 (Liveliness) |
AUTOMATIC 或 MANUAL_BY_NODE/MANUAL_BY_TOPIC |
监测节点/话题是否存活 |
Python 中配置 QoS:
1 2 3 4 5 6 7 8 9
| from rclpy.qos import QoSProfile, ReliabilityPolicy, DurabilityPolicy
qos_profile = QoSProfile( reliability=ReliabilityPolicy.RELIABLE, durability=DurabilityPolicy.TRANSIENT_LOCAL, history=KEEP_LAST(10), depth=10 ) publisher = self.create_publisher(String, 'topic_name', qos_profile)
|
C++ 中配置 QoS:
1 2 3 4 5
| rclcpp::QoS qos(10); qos.reliability(rclcpp::ReliabilityPolicy::Reliable); qos.durability(rclcpp::DurabilityPolicy::TransientLocal);
auto publisher = this->create_publisher<std_msgs::msg::String>("chatter", qos);
|
1. 自定义消息接口 (Message)
① 创建接口功能包
1 2 3
| ros2 pkg create xxx_interfaces --build-type ament_cmake \ --dependencies rosidl_default_generators builtin_interfaces \ --license Apache-2.0
|
rosidl_default_generators:用于将自定义的消息文件转换为 C++/Python 源码模块
builtin_interfaces:用于表示记录信息时间
② 定义消息文件
- 文件格式为
.msg,存放于功能包 msg/ 目录下
- 文件命名需大写字母开头
- 语法与 C++ 定义变量类似
③ 在 CMakeLists.txt 中注册
1 2 3 4
| rosidl_generate_interfaces(${PROJECT_NAME} "msg/Xxx.msg" DEPENDENCIES builtin_interfaces )
|
④ 在 package.xml 中添加声明
1
| <member_of_group>rosidl_interface_packages</member_of_group>
|
⑤ 验证接口是否构建成功
1
| ros2 interface show xxx_interfaces/msg/Xxx
|
2. 话题通信 (Topic)
📤 发布话题(异步通信)
常见用途:传感器数据发布、运动控制指令等
终端操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ros2 run <package_name> <node_name>
ros2 topic list
ros2 topic info <topic_name>
ros2 topic hz <topic_name>
ros2 topic bw <topic_name>
ros2 topic echo <topic_name>
ros2 topic pub <topic_name> <msg_type> <msg_data>
|
在节点类中调用(Python):
1
| self.create_publisher(String, 'topic_name', 10)
|
- 第一个参数:话题接口类型
- 第二个参数:话题名称
- 第三个参数:QoS 队列深度(服务质量策略)
在节点类中调用(C++):
1
| publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);
|
发布示例(Python):
1 2 3 4
| self.publisher_ = self.create_publisher(String, 'topic_name', 10) msg = String() msg.data = 'Hello ROS2' self.publisher_.publish(msg)
|
发布示例(C++):
1 2 3 4 5 6 7 8 9 10
| rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);
auto msg = std_msgs::msg::String(); msg.data = "Hello ROS2"; publisher_->publish(msg);
|
📥 订阅话题
在节点类中调用(Python):
1
| self.create_subscription(String, 'topic_name', topic_callback, 10)
|
- 第一个参数:话题接口类型
- 第二个参数:话题名称
- 第三个参数:回调函数(收到消息时自动调用,消息数据作为参数传入)
- 第四个参数:QoS 队列深度
在节点类中调用(C++):
1 2 3 4
| subscription_ = this->create_subscription<std_msgs::msg::String>( "chatter", 10, std::bind(&SubscriberNode::topic_callback, this, std::placeholders::_1) );
|
订阅示例(Python):
1 2 3 4 5 6 7 8 9 10 11
| def __init__(self): super().__init__("subscriber_node") self.subscription_ = self.create_subscription( String, 'topic_name', self.topic_callback, 10 )
def topic_callback(self, msg): self.get_logger().info(f'Received: {msg.data}')
|
订阅示例(C++):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include "rclcpp/rclcpp.hpp" #include "std_msgs/msg/string.hpp"
class SubscriberNode : public rclcpp::Node { public: SubscriberNode();
private: void topic_callback(const std_msgs::msg::String::SharedPtr msg); rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_; };
SubscriberNode::SubscriberNode() : Node("subscriber_node") { subscription_ = this->create_subscription<std_msgs::msg::String>( "chatter", 10, std::bind(&SubscriberNode::topic_callback, this, std::placeholders::_1) ); }
void SubscriberNode::topic_callback(const std_msgs::msg::String::SharedPtr msg) { RCLCPP_INFO(this->get_logger(), "Received: %s", msg->data.c_str()); }
|
3. 自定义服务接口 (Service)
① 创建接口功能包
1 2 3
| ros2 pkg create xxx_interfaces --build-type ament_cmake \ --dependencies rosidl_default_generators \ --license Apache-2.0
|
② 定义服务文件
- 文件格式为
.srv,存放于功能包 srv/ 目录下
- 文件命名需大写字母开头
- 用
--- 分隔,上部分为 request,下部分为 response
③ 在 CMakeLists.txt 中注册
1 2 3
| rosidl_generate_interfaces(${PROJECT_NAME} "srv/Xxx.srv" )
|
④ 在 package.xml 中添加声明
1
| <member_of_group>rosidl_interface_packages</member_of_group>
|
⑤ 验证接口是否构建成功
1
| ros2 interface show xxx_interfaces/srv/Xxx
|
4. 服务通信 (Service)
🖥️ 创建服务端
在节点类中调用:
1
| self.create_service(AddTwoInts, 'service_name', service_callback)
|
- 第一个参数:服务接口类型
- 第二个参数:服务名称
- 第三个参数:回调函数(收到请求时自动调用,传入 request 和 response 对象)
💻 创建客户端
在节点类中调用:
1
| self.create_client(AddTwoInts, 'service_name')
|
- 第一个参数:服务接口类型
- 使用
wait_for_service() 等待服务可用
- 构建请求数据并调用
call_async() 发送异步请求
5. 自定义动作接口 (Action)
① 创建接口功能包
1 2 3
| ros2 pkg create xxx_interfaces --build-type ament_cmake \ --dependencies rosidl_default_generators builtin_interfaces \ --license Apache-2.0
|
② 定义动作文件
- 文件格式为
.action,存放于功能包 action/ 目录下
- 文件命名需大写字母开头
- 用
--- 分隔三部分:goal(目标)、result(结果)、feedback(反馈)
示例:Fibonacci.action:
1 2 3 4 5
| int32 order --- int32[] sequence --- int32[] partial_sequence
|
- 第 1 部分
order:客户端请求的斐波那契数列阶数(Goal)
- 第 2 部分
sequence:服务端返回的完整数列(Result)
- 第 3 部分
partial_sequence:服务端逐步返回的中间结果(Feedback)
③ 在 CMakeLists.txt 中注册
1 2 3 4
| rosidl_generate_interfaces(${PROJECT_NAME} "action/Fibonacci.action" DEPENDENCIES builtin_interfaces )
|
④ 在 package.xml 中添加声明
1
| <member_of_group>rosidl_interface_packages</member_of_group>
|
⑤ 验证接口是否构建成功
1
| ros2 interface show xxx_interfaces/action/Fibonacci
|
6. 动作通信 (Action)
🤖 创建动作服务端
1
| self.create_action(Fibonacci, 'action_name', action_callback)
|
- 第一个参数:动作接口类型
- 第二个参数:动作名称
- 第三个参数:回调函数(处理目标请求,返回结果)
回调函数格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def execute_callback(self, goal_handle): self.get_logger().info('Executing goal...') feedback = Fibonacci.Feedback() feedback.partial_sequence = [0, 1] goal_handle.publish_feedback(feedback) goal_handle.succeed() result = Fibonacci.Result() result.sequence = [0, 1] return result
|
🎯 创建动作客户端
1 2 3 4 5 6
| self._action_client = ActionClient(self, Fibonacci, 'action_name')
goal_msg = Fibonacci.Goal() goal_msg.order = 10 self._action_client.send_goal_async(goal_msg)
|
7. 参数 (Parameter)
📝 参数声明与获取
1 2 3 4 5
| self.declare_parameter('param_name', default_value)
self.get_parameter('param_name').get_value()
|
▶️ 启动时指定参数
1
| ros2 run pkg_name node_name --ros-args -p param_name:=value
|
🔔 订阅参数更新
1 2
| self.add_on_set_parameters_callback(callback_function)
|
当参数被更新时会调用回调函数:
1 2 3 4 5 6
| def callback_function(self, params): for param in params: if param.name == 'param_name': pass return SetParametersResult(successful=True)
|
终端修改参数值:
1
| ros2 param set /node_name param_name value
|
六、 Launch 脚本启动多个节点
① 在功能包的 launch/ 目录下创建 xxx.launch.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import launch import launch_ros
def generate_launch_description(): return launch.LaunchDescription([ launch_ros.actions.Node( package='pkg_name', executable='node_name', name='node_instance_name_1', output='screen', parameters=[{'param_name': 'value1'}] ), launch_ros.actions.Node( package='pkg_name', executable='node_name', name='node_instance_name_2', output='screen', parameters=[{'param_name': 'value2'}] ) ])
|
② 在 CMakeLists.txt 中添加 launch 文件安装指令
1 2 3 4
| install( DIRECTORY launch DESTINATION share/${PROJECT_NAME}/ )
|
③ 编译后启动
1
| ros2 launch pkg_name xxx.launch.py
|
📊 版本迭代记录
| 版本 |
日期 |
更新内容 |
| v0.0.1 |
2026-01-15 |
初始版本,包含工作空间、功能包结构、常用指令、通信、Launch 与节点基础内容 |
| v0.0.2 |
2026-04-03 |
优化整体结构:统一编号体系、修复代码块格式、修正 srv 目录笔误、补充 QoS 说明、增加版本迭代记录 |
| v0.0.3 |
2026-04-07 |
新增节点定义章节:Python 类继承式节点、C++ 类继承式节点,逐行注释详解;修复原代码语法错误(get_longger → get_logger、rclcpp::ok → rclcpp::ok()、this.get_logger → this->get_logger、类定义缺冒号等);调整章节顺序,将节点移至通信机制之前 |
| v0.0.4 |
2026-04-09 |
修复代码块格式问题;新增 Action 动作通信机制章节;新增 QoS 服务质量策略章节(可靠性、持久性、历史等配置);完善话题部分 C++ 代码示例(发布者/订阅者完整实现) |