系统版本: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

# 查看当前活跃的话题列表(附加 -v 参数可显示详细的消息类型信息)
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
# -----------------------------
# 1. 创建功能包
# -----------------------------
# <build-type> 可填入 ament_cmake (C++包) 或 ament_python (Python包)
ros2 pkg create --build-type <build-type> <package_name>

# -----------------------------
# 2. 编译指定功能包
# -----------------------------
# 注意:使用 packages-select 前需确保已安装 colcon 扩展功能
# 安装命令:sudo apt install python3-colcon-common-extensions
cd ~/dev_ws
colcon build --packages-select <package_name>

# -----------------------------
# 3. 清除工作空间编译历史
# -----------------------------
# 如果需要重新彻底编译,可删除生成的构建和安装目录
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import rclpy # ROS2 的 Python 客户端库
from rclpy.node import Node # Node 基类
import time # 用于延时

class HelloWorldNode(Node):
"""自定义节点类,继承 rclpy.node.Node"""

def __init__(self, name):
# 调用父类构造函数,注册节点名称
super().__init__(name)

# 循环打印日志(简单演示用,实际项目推荐用定时器 create_timer)
while rclpy.ok():
self.get_logger().info("Hello World")
time.sleep(0.5)


def main(args=None):
# ① 初始化 ROS2 通信系统
rclpy.init(args=args)

# ② 实例化自定义节点
node = HelloWorldNode("node_helloworld_class")

# ③ 启动事件循环(spin 会持续调度回调)
rclpy.spin(node)

# ④ 销毁节点并关闭 ROS2
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") // 调用父类构造函数,注册节点名称
{
// 循环打印日志(简单演示用,实际项目推荐用 create_wall_timer)
while (rclcpp::ok())
{
RCLCPP_INFO(this->get_logger(), "Hello World");
sleep(1);
}
}
};

int main(int argc, char* argv[])
{
// ① 初始化 ROS2 通信系统
rclcpp::init(argc, argv);

// ② 创建节点实例并启动事件循环
// std::make_shared 创建智能指针管理的节点对象
rclcpp::spin(std::make_shared<HelloWorldNode>());

// ③ 关闭 ROS2 系统
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) AUTOMATICMANUAL_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
// 头文件 SubscriberNode.hpp
#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.cpp
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_longgerget_loggerrclcpp::okrclcpp::ok()this.get_loggerthis->get_logger、类定义缺冒号等);调整章节顺序,将节点移至通信机制之前
v0.0.4 2026-04-09 修复代码块格式问题;新增 Action 动作通信机制章节;新增 QoS 服务质量策略章节(可靠性、持久性、历史等配置);完善话题部分 C++ 代码示例(发布者/订阅者完整实现)