Files
ROS2_learn/doc/10-concepts.md
T
2026-08-03 18:09:35 +08:00

25 KiB

10 · ROS2 核心概念速览(完整概念地图)

目标:30 分钟内把 ROS2 所有核心概念装进脑子里,后续文档都能"秒懂"。


目录


一图总览

┌──────────────────────────────────────────────────────────────────┐
│                            DDS 总线 (默认 fastdds)                 │
│                                                                  │
│   Topic /chatter     Topic /image_raw     Topic /tf               │
│   Topic /joint_states   Service /add_two_ints                    │
│   Action /fibonacci   Parameter server                          │
└───────┬─────────────────┬──────────────────┬─────────────────────┘
        │                 │                  │
        ▼                 ▼                  ▼
   ┌─────────┐     ┌──────────┐       ┌───────────┐
   │ talker  │     │ camera   │       │ tf listener│
   │ listener│     │processor │       │joint_pub   │
   │  (Node) │     │  (Node)  │       │  (Node)    │
   └─────────┘     └──────────┘       └───────────┘

6 大概念:Node / Topic / Service / Action / Parameter / TF。 下面 12 节拆解。


1. 计算图(Computational Graph)

1.1 是什么

ROS2 程序由节点(Node)组成,节点之间通过话题/服务/动作连接,形成一张"计算图"。 图不是静态配置,是运行时由 DDS 自动发现

1.2 看计算图

# 命令行看拓扑
ros2 node list
ros2 topic list
ros2 service list
ros2 action list

# 可视化(GUI)
ros2 run rqt_graph rqt_graph

1.3 三个关键术语

术语 含义
Node 一个独立运行的程序
Edge Node 之间的连接(Topic / Service / Action)
Discovery DDS 自动找节点,无需中心注册

2. Node(节点)

2.1 是什么

ROS2 程序的基本单元。一个 Node = 一个进程 = 一项业务功能。

2.2 怎么写(Python)

import rclpy
from rclpy.node import Node

class MyNode(Node):
    def __init__(self):
        super().__init__('my_node_name')   # 节点名,在 ROS Domain 内必须唯一
        # 在这里创建 publisher / subscription / timer / param
        self.timer_ = self.create_timer(0.5, self.tick)

    def tick(self):
        self.get_logger().info('tick')

def main():
    rclpy.init()                                  # 全局初始化
    node = MyNode()
    rclpy.spin(node)                               # 进入事件循环,阻塞
    rclpy.shutdown()                               # 退出

if __name__ == '__main__':
    main()

2.3 怎么写(C++)

#include "rclcpp/rclcpp.hpp"

class MyNode : public rclcpp::Node {
public:
  MyNode() : rclcpp::Node("my_node_name") {
    timer_ = this->create_wall_timer(
      500ms, std::bind(&MyNode::tick, this));
  }
private:
  void tick() {
    RCLCPP_INFO(this->get_logger(), "tick");
  }
  rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char * argv[]) {
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MyNode>());
  rclcpp::shutdown();
  return 0;
}

2.4 关键 API

API 用途
super().__init__('name') 节点构造时设名字
create_publisher(...) 创建发布者
create_subscription(...) 创建订阅者
create_service(...) 创建服务端
create_action_server(...) 创建动作服务端
create_timer(period, cb) 创建定时器
declare_parameter(...) 声明参数
get_logger().info(...) 打日志
get_clock() 取时钟
rclpy.spin(node) 进入事件循环,处理所有回调

2.5 生命周期

rclpy.init()           ─┐
                       ├─ 实例化 Node ─ 创建 pub/sub/service ─ spin 等待回调
实例化 Node           ─┘
        │
        ▼
rclpy.spin(node)       ── 阻塞,处理 timer / subscription / service 回调
        │
        ▼ (Ctrl+C 或 shutdown)
rclpy.shutdown()       ── 清理

2.6 本仓库对照


3. Topic(话题)

3.1 是什么

异步、多对多、单向的发布订阅通道。

  • 异步:publish() 不阻塞
  • 多对多:1 个 publisher,任意多个 subscriber
  • 单向:消息只从 pub 到 sub,反方向不通

3.2 通信模型

Publisher ──publish()──> Topic (chatter) ──callback(msg)──> Subscriber
        ────────────────────────────────────────────────────
        异步,非阻塞     多对多,单向       事件循环触发

3.3 vs 其他通信方式

Topic Service Action
同步 异步 同步 异步
一对多
反馈
取消 N/A

3.4 Python API

# Publisher
pub = node.create_publisher(
    std_msgs.msg.String,    # 消息类型
    'chatter',               # topic 名
    10                       # QoS 深度
)

# 构造 + 发送
msg = std_msgs.msg.String()
msg.data = 'hello'
pub.publish(msg)            # 异步,不等任何东西

# Subscriber
sub = node.create_subscription(
    std_msgs.msg.String,    # 类型
    'chatter',               # topic 名
    callback,                # 回调函数
    10                       # QoS 深度
)

def callback(msg):
    node.get_logger().info(f'recv: {msg.data}')

3.5 C++ API

// Publisher
auto pub = node->create_publisher<std_msgs::msg::String>("chatter", 10);

auto msg = std_msgs::msg::String();
msg.data = "hello";
pub->publish(msg);

// Subscriber
auto sub = node->create_subscription<std_msgs::msg::String>(
    "chatter", 10,
    [this](const std_msgs::msg::String::SharedPtr msg) {
      RCLCPP_INFO(this->get_logger(), "recv: %s", msg->data.c_str());
    });

3.6 QoS(服务质量)速查

维度 取值 默认 影响
Reliability RELIABLE / BEST_EFFORT RELIABLE 必须 pub/sub 一致
History KEEP_LAST(N) / KEEP_ALL KEEP_LAST(10) 队列大小
Durability VOLATILE / TRANSIENT_LOCAL VOLATILE 晚订阅者是否收到旧数据

兼容规则:

  • RELIABLE ↔ RELIABLE
  • BEST_EFFORT ↔ BEST_EFFORT
  • BEST_EFFORT → RELIABLE (sub 容忍丢)
  • RELIABLE → BEST_EFFORT (sub 不发 ACK,pub 报错)

3.7 常用 CLI

ros2 topic list                   # 所有 topic
ros2 topic info <topic> -v        # 类型 + pub/sub 列表
ros2 topic echo <topic>           # 实时打印
ros2 topic hz <topic>             # 频率 Hz
ros2 topic bw <topic>             # 带宽 bytes/s
ros2 topic pub <topic> <type> "<msg>" --once   # 发一条测试
ros2 bag record <topic>           # 录包
ros2 bag play <bag>               # 回放

3.8 本仓库对照

深度: doc/20-topics.md


4. Service(服务)

4.1 是什么

同步、一对一、双向的请求/响应。

  • 一次性调用 + 等结果
  • 几毫秒到几秒

4.2 通信模型

Client ──call(req)──> Server
       ◀──response────
       同步(阻塞),一次一答

4.3 .srv 文件定义

# example_interfaces/srv/AddTwoInts.srv
int64 a       # Request 字段
int64 b
---
int64 sum     # Response 字段
  • --- 上 = Request
  • --- 下 = Response

4.4 Server 端

from example_interfaces.srv import AddTwoInts

srv = node.create_service(
    AddTwoInts,                  # 服务类型
    'add_two_ints',               # 服务名
    callback                      # 回调签名:callback(req, resp) -> resp
)

def callback(request, response):
    response.sum = request.a + request.b
    return response               # 必须 return response

4.5 Client 端(异步风格)

client = node.create_client(AddTwoInts, 'add_two_ints')

while not client.wait_for_service(timeout_sec=1.0):
    node.get_logger().info('waiting...')

req = AddTwoInts.Request()
req.a = 12; req.b = 30
future = client.call_async(req)

# 必须 spin 让 future 完成
rclpy.spin_until_future_complete(node, future, timeout_sec=5.0)
result = future.result()
print(result.sum)                # 42

4.6 何时用

场景 用 Service
拍照(几 ms)
关节角度查询
计算查询
周期性相机帧 → Topic
长任务(几秒到几分钟) → Action

4.7 常用 CLI

ros2 service list
ros2 service type <name>          # 类型
ros2 service call <name> <type> "<req>"
# 例:
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 12, b: 30}"

深度: doc/30-services.md


5. Action(动作)

5.1 是什么

Service 的"长任务"版本:

  • client 发 Goal
  • server 周期性推 Feedback
  • 最终返回 Result
  • client 可中途 cancel

适合:抓取、导航、SLAM。

5.2 通信模型

Client                Server
   │  Goal              │
   ├────────────────────>│
   │                     ▼ 执行中
   │   Feedback          │
   │<────────────────────┤
   │   Feedback          │
   │<────────────────────┤
   │   Result            │
   │<────────────────────┤
   │  cancel (可选)       │
   ├────────────────────>│

5.3 .action 文件定义

# example_interfaces/action/Fibonacci.action
int32 order
---
int32[] sequence     # Result
---
int32[] sequence     # Feedback
含义
第一段 Goal(client 发)
--- 分隔
第二段 Result(server 最终给一次)
--- 分隔
第三段 Feedback(server 周期性推 0..N 次)

5.4 Server 端

import rclpy.action

action_server = rclpy.action.ActionServer(
    node,
    Fibonacci,              # ActionType
    'fibonacci',            # action 名
    execute_callback        # 签名:cb(goal_handle) -> result
)

def execute_callback(goal_handle):
    order = goal_handle.request.order
    feedback = Fibonacci.Feedback()
    result = Fibonacci.Result()
    sequence = [0, 1]

    for i in range(1, order):
        if goal_handle.is_cancel_requested:
            goal_handle.canceled()
            return Fibonacci.Result()
        sequence.append(sequence[i] + sequence[i-1])
        feedback.sequence = sequence
        goal_handle.publish_feedback(feedback)

    goal_handle.succeed()
    result.sequence = sequence
    return result

5.5 Client 端

client = ActionClient(node, Fibonacci, 'fibonacci')
client.wait_for_server()

goal = Fibonacci.Goal()
goal.order = 6

future = client.send_goal_async(
    goal,
    feedback_callback=lambda msg: print('fb:', msg.feedback.sequence)
)
future.add_done_callback(cb_goal_response)

5.6 状态机

PENDING  → ACCEPTED  → EXECUTING  →  SUCCEEDED
                                      ABORTED
                                      CANCELED

5.7 MultiThreadedExecutor(关键!)

Action server 的 execute + publish_feedback 都在主线程。用 MultiThreadedExecutor 避免 Feedback 卡死:

from rclpy.executors import MultiThreadedExecutor
executor = MultiThreadedExecutor(num_threads=4)
executor.add_node(node)
executor.spin()

5.8 何时用 vs Service

持续
< 几秒 Service
几秒 ~ 几小时 Action

深度: doc/40-actions.md


6. Parameter(参数)

6.1 是什么

节点的运行时配置项。声明后可以运行时改,不重编译。

6.2 声明 / 读取 / 写

class Talker(Node):
    def __init__(self):
        super().__init__('talker_py')
        # 声明参数 + 默认值
        self.declare_parameter('period_ms', 500)
        self.declare_parameter('topic', 'chatter')

        # 读取
        period = self.get_parameter('period_ms').value
        topic = self.get_parameter('topic').value

    def change_param(self, new_period):
        # 运行时改
        param = rclpy.parameter.Parameter(
            'period_ms', rclpy.Parameter.Type.INTEGER, new_period
        )
        self.set_parameters([param])

6.3 CLI 改参数

ros2 param list                # 节点的所有参数
ros2 param get /talker_py period_ms
ros2 param set /talker_py period_ms 200
ros2 param describe /talker_py period_ms
ros2 param dump /talker_py > params.yaml    # 导出
ros2 param load /talker_py params.yaml      # 加载

6.4 launch 中覆盖

Node(
    package='py_pubsub',
    executable='talker',
    parameters=[{'period_ms': 200, 'topic': 'chatter'}]   # 覆盖
)

CLI 启动:

ros2 run py_pubsub talker --ros-args -p period_ms:=200 -p topic:=hello

6.5 YAML 文件

# config/params.yaml
talker_py:
  ros__parameters:
    period_ms: 200
    topic: chatter
Node(package='py_pubsub', executable='talker',
     parameters=[get_package_share_directory('my_pkg') + '/config/params.yaml'])

7. TF2(坐标变换)

7.1 是什么

管理机器人所有坐标系之间相对位姿的工具。维护一棵 TF tree

7.2 典型 TF tree

world
  └─ map (SLAM)
      └─ odom (AMCL / 里程计)
          └─ base_link (底盘)
              ├─ base_footprint
              ├─ laser_frame (激光雷达)
              ├─ camera_optical_frame (相机)
              └─ arm_base (机械臂)
                  └─ shoulder (关节 1)
                      └─ upper_arm (关节 2)
                          └─ wrist (关节 3)
                              └─ gripper (夹爪)

7.3 C++ Listener API

#include "tf2_ros/buffer.h"
#include "tf2_ros/transform_listener.h"

tf2_ros::Buffer buffer(node->get_clock());
tf2_ros::TransformListener listener(buffer, node);

// 查"gripper 在 base_link 下"
geometry_msgs::msg::TransformStamped tf = buffer.lookupTransform(
    "base_link",              // target frame
    "gripper",                // source frame
    tf2::TimePointZero);      // latest

RCLCPP_INFO(node->get_logger(),
    "gripper in base_link: x=%.3f y=%.3f z=%.3f",
    tf.transform.translation.x, ...);

7.4 几何变换(把点变换到其他系)

#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"

geometry_msgs::msg::PointStamped p_in, p_out;
p_in.header.frame_id = "world";
p_in.point.x = 1.0; p_in.point.y = 2.0; p_in.point.z = 3.0;

p_out = buffer.transform(p_in, "robot");
// p_out 在 robot 系下

7.5 Python Listener

from tf2_ros import Buffer, TransformListener

buffer = Buffer()
listener = TransformListener(buffer, node)

try:
    tf = buffer.lookup_transform(
        'base_link', 'gripper', rclpy.time.Time(),
        timeout=rclpy.duration.Duration(seconds=1.0))
    print(tf.transform.translation)
except Exception as e:
    node.get_logger().warn(f'lookup failed: {e}')

7.6 静态 TF(不变的关系)

# 命令行:激光雷达固定在底盘
ros2 run tf2_ros static_transform_publisher \
  --x 0.1 --y 0 --z 0.2 --roll 0 --pitch 0 --yaw 0 \
  --frame-id base_link --child-frame-id laser_frame
# launch 里
Node(package='tf2_ros', executable='static_transform_publisher',
     arguments=['--x', '0.1', '--y', '0', '--z', '0.2',
                '--frame-id', 'base_link', '--child-frame-id', 'laser_frame'])

7.7 动态 TF(URDF + JointState)

robot_state_publisher (ROS2 系统包):

  • 输入:URDF + /joint_states
  • 输出:动态 TF 到 /tf
sudo apt install ros-humble-robot-state-publisher

ros2 run robot_state_publisher robot_state_publisher \
  --ros-args -p robot_description:="$(xacro arm.urdf)"

7.8 调试命令

ros2 run tf2_tools view_frames       # 生成 frames.pdf
ros2 run tf2_ros tf2_echo base_link gripper   # 实时打印
ros2 topic hz /tf                    # /tf 频率
ros2 topic info /tf_static -v        # 静态 TF 列表

深度: doc/50-tf2.md


8. URDF(机器人模型)

8.1 是什么

XML 格式的机器人描述:link / joint / 视觉 / 碰撞 / 物理参数。

8.2 最小 URDF

<?xml version="1.0"?>
<robot name="my_arm">
  <link name="base_link">
    <visual><geometry><box size="0.1 0.1 0.1"/></geometry></visual>
    <collision><geometry><box size="0.1 0.1 0.1"/></geometry></collision>
    <inertial><mass value="1.0"/><inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/></inertial>
  </link>

  <joint name="joint1" type="revolute">
    <parent link="base_link"/>
    <child  link="link1"/>
    <origin xyz="0 0 0.05"/>
    <axis   xyz="0 0 1"/>
    <limit  lower="-3.14" upper="3.14" effort="1.0" velocity="1.0"/>
  </joint>

  <link name="link1">
    <visual><geometry><box size="0.05 0.05 0.1"/></geometry></visual>
    <collision><geometry><box size="0.05 0.05 0.1"/></geometry></collision>
    <inertial><mass value="0.5"/><inertia ixx="0.005" ixy="0" ixz="0" iyy="0.005" iyz="0" izz="0.005"/></inertial>
  </link>
</robot>

8.3 关节类型

type 含义 DoF
revolute 转动(有限位) 1
continuous 转动(无限位) 1
prismatic 滑动 1
fixed 固定 0
floating 6 DoF 6

8.4 校验

sudo apt install liburdfdom-tools
check_urdf my_arm.urdf           # 校验 URDF
urdf_to_graphiz my_arm.urdf     # 生成 PDF/PNG 图

深度: doc/60-urdf.md


9. sensor_msgs(常用消息类型)

消息 关键字段 用途
std_msgs/String string data 通用
std_msgs/Header stamp, frame_id 时间戳 + 坐标系
sensor_msgs/Image height/width/encoding/step/data 相机图像
sensor_msgs/CameraInfo K/D/R/P 相机内参/外参
sensor_msgs/PointCloud2 height/width/data/is_bigendian 3D 点云
sensor_msgs/JointState name[]/position[]/velocity[]/effort[] 关节状态
sensor_msgs/Imu orientation/angular_velocity/linear_acceleration IMU
geometry_msgs/Pose Point position + Quaternion orientation 位姿
geometry_msgs/PoseStamped Header + Pose 带时间戳位姿
geometry_msgs/Twist Vector3 linear + Vector3 angular 速度指令
geometry_msgs/Transform Vector3 translation + Quaternion rotation TF 变换
nav_msgs/Odometry pose + twist + covariances 里程计
nav_msgs/Path PoseStamped[] 路径
trajectory_msgs/JointTrajectory JointTrajectoryPoint[] 关节轨迹(ros2_control 用)
trajectory_msgs/JointTrajectoryPoint positions[]/velocities[]/accelerations[]/effort[] 单点轨迹
vision_msgs/Detection2DArray detections[] 目标检测结果

10. Launch(启动文件)

10.1 是什么

Python 脚本,描述"启动哪些节点 + 参数",取代 ROS1 XML。

10.2 最小 launch

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='my_pkg',
            executable='my_node',
            name='my_node',
            output='screen',
            parameters=[{'param1': 'value1'}],
        ),
    ])

10.3 启动方式

ros2 launch <pkg> <launch_file.py>
ros2 launch <pkg> <launch_file.py> topic:=hello period_ms:=200

深度: doc/70-launch.md


11. DDS / QoS / 时间(底层)

11.1 DDS 是什么

ROS2 默认用 DDS(Data Distribution Service)做底层通信。 本仓库默认 FastDDS(rmw_fastrtps_cpp)。

DDS 提供的核心能力:

  • 自动节点发现(基于 UDP multicast)
  • 多种 QoS(可靠 / 尽力而为)
  • 实时性

11.2 RMW(ROS Middleware)

ROS2 用 RMW 抽象层,RMW 是 DDS 的 ROS 包装:

RMW 实现 适用
rmw_fastrtps_cpp ros-humble-rmw-fastrtps-cpp(默认) 通用
rmw_cyclonedds_cpp ros-humble-rmw-cyclonedds-cpp 跨网段更稳

切换:

export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp

11.3 时间

概念 含义 用法
system time 墙钟时间(默认) 仿真时需配 use_sim_time: true
sim time 仿真时钟(/clock topic) Gazebo / 录包回放
Header.stamp 消息时间戳 TF 同步、消息时间关联

12. 6 大概念怎么一起工作

   ┌────────────────────────────────────────────────┐
   │               application                       │
   │  (决策 / 控制 / 感知 / 学习)                  │
   └─────────┬──────────────────────────────────────┘
             │ 用 TF / Topic / Service / Action / Parameter
   ┌─────────▼──────────────────────────────────────┐
   │              ROS2 中间件 (rclcpp/rclpy)         │
   │  ┌────────────────────────────────────────┐   │
   │  │  Topic  Pub/Sub (异步多对多单向)         │   │
   │  │  Service Req/Resp (同步一对一双向)      │   │
   │  │  Action  Goal/Feedback/Result (长任务)  │   │
   │  │  Param   配置                           │   │
   │  │  TF2     坐标变换树                     │   │
   │  └────────────────────────────────────────┘   │
   │  ┌────────────────────────────────────────┐   │
   │  │       DDS (默认 fastdds)               │   │
   │  └────────────────────────────────────────┘   │
   └────────────────────────────────────────────────┘
             │ 走 UDP multicast / unicast
   ┌─────────▼──────────────────────────────────────┐
   │                网络                             │
   └─────────────────────────────────────────────────┘

13. 概念速查表

概念 一句话 API 深度文档
Node 一个进程 = 一项业务 rclpy.spin(node) 本篇 §2
Topic 异步多对多单向 create_publisher / create_subscription 20-topics
Service 同步 1对1 双向 create_service / create_client 30-services
Action 长任务 + 反馈 + 可取消 ActionServer / ActionClient 40-actions
Parameter 运行时配置 declare_parameter / set_parameter 本篇 §6
TF2 坐标系树 Buffer.lookup_transform 50-tf2
URDF 机器人 XML 描述 check_urdf 60-urdf
Launch 启动脚本 ros2 launch <pkg> <file> 70-launch
DDS 通信中间件 RMW_IMPLEMENTATION= 本篇 §11

接下来读

按主题深读:

你想深挖什么
Topic 实现细节、QoS、跨语言互通 20-topics.md
Service 实现、req/resp 时序 30-services.md
Action 三件套、cancel、MultiThreadedExecutor 40-actions.md
TF tree / lookup / static_transform 50-tf2.md
URDF / xacro / robot_state_publisher 60-urdf.md
launch 嵌套、参数覆盖、事件 70-launch.md
三机部署(PC + RDK X5 + RK3506) 100-embedded-deployment.md
进入具身智能 / VLA / 机器人 99-embodied-ai.md