ros学习

ros学习

1.创建项目

创建常用命令
复制代码
# 1. 创建工作空间目录(命名建议:xxx_ws,如ros2_ws)
mkdir -p ~/ros2_ws/src

# 2. 进入工作空间目录
cd ~/ros2_ws

# 3. 初始化工作空间(仅需执行1次)
colcon build


# 进入src目录(必须在src下创建功能包)
cd ~/ros2_ws/src

# 通用命令模板
ros2 pkg create <包名> --build-type <构建类型> --dependencies <依赖包> [--node-name <节点名>]

# 示例1:创建C++功能包(最常用)
ros2 pkg create my_cpp_pkg --build-type ament_cmake --dependencies rclcpp std_msgs

# 示例2:创建Python功能包
ros2 pkg create my_py_pkg --build-type ament_python --dependencies rclpy std_msgs

# 示例3:创建带默认节点的Python包(快捷方式)
ros2 pkg create my_py_node --build-type ament_python --dependencies rclpy --node-name my_first_node
编译常用命令
复制代码
# 回到工作空间根目录
cd ~/ros2_ws

# 编译所有功能包
colcon build

# 编译指定功能包(推荐,节省时间)
colcon build --packages-select my_cpp_pkg

# 编译Python包时自动安装依赖(可选)
colcon build --packages-select my_py_pkg --symlink-install
创建节点模板
my_first_node.py
复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ROS2 Python 通用节点模板
功能包含:日志输出、参数管理、话题发布/订阅、定时器、服务端/客户端、节点销毁处理
适配ROS2 Humble/Foxy/Iron版本
"""

# 导入ROS2核心库
import rclpy
from rclpy.node import Node
from rclpy.parameter import Parameter
from rclpy.executors import SingleThreadedExecutor
from rclpy.callback_groups import MutuallyExclusiveCallbackGroup

# 导入标准消息类型(根据需求增减)
from std_msgs.msg import String, Int32
from std_srvs.srv import SetBool  # 标准服务类型(布尔值设置)

# 自定义话题消息(如需自定义,需先创建msg文件并配置)
# from my_py_pkg.msg import CustomMsg  # 示例:导入自定义消息

class MyFirstNode(Node):
    """
    自定义ROS2节点类,继承自rclpy.node.Node
    封装所有核心功能,便于维护和扩展
    """
    def __init__(self):
        # 1. 初始化节点,指定节点名称(必须唯一)
        super().__init__("my_first_node")
        self.get_logger().info("=== 自定义节点已启动 ===")

        # 2. 参数管理(声明+获取,支持动态修改)
        # 声明参数(参数名,默认值,参数类型)
        self.declare_parameter("node_name", "my_first_node", Parameter.Type.STRING)
        self.declare_parameter("publish_frequency", 1.0, Parameter.Type.DOUBLE)  # 发布频率(Hz)
        self.declare_parameter("max_count", 100, Parameter.Type.INTEGER)

        # 获取参数值(带默认值兜底,防止参数未设置)
        self.node_name = self.get_parameter("node_name").get_parameter_value().string_value
        self.pub_freq = self.get_parameter("publish_frequency").get_parameter_value().double_value
        self.max_count = self.get_parameter("max_count").get_parameter_value().integer_value

        # 打印参数信息
        self.get_logger().info(f"节点名称: {self.node_name}")
        self.get_logger().info(f"发布频率: {self.pub_freq} Hz")
        self.get_logger().info(f"最大计数: {self.max_count}")

        # 3. 初始化变量
        self.count = 0  # 计数器(用于定时器发布数据)

        # 4. 话题发布器(创建发布者)
        # 参数:话题名、消息类型、队列大小(防止消息丢失)
        self.string_pub = self.create_publisher(String, "chatter", 10)
        self.int_pub = self.create_publisher(Int32, "count_topic", 10)

        # 5. 话题订阅器(创建订阅者)
        # 参数:话题名、消息类型、回调函数、队列大小
        self.sub = self.create_subscription(
            String, 
            "input_topic", 
            self.sub_callback,  # 订阅回调函数
            10
        )

        # 6. 定时器(周期性执行任务,如发布话题)
        # 参数:定时器周期(秒)、回调函数、回调组(可选,用于并发)
        self.timer = self.create_timer(
            1.0 / self.pub_freq,  # 周期 = 1/频率
            self.timer_callback,
            callback_group=MutuallyExclusiveCallbackGroup()
        )

        # 7. 服务端(提供服务,响应客户端请求)
        self.srv_server = self.create_service(
            SetBool,  # 服务类型
            "reset_count",  # 服务名
            self.srv_callback  # 服务回调函数
        )

        # 8. 服务客户端(调用其他节点的服务,示例)
        self.srv_client = self.create_client(SetBool, "other_node_service")
        # 等待服务端上线(最多等待5秒)
        if not self.srv_client.wait_for_service(timeout_sec=5.0):
            self.get_logger().warn("未找到服务 'other_node_service',跳过客户端初始化")

    def timer_callback(self):
        """
        定时器回调函数:周期性发布话题数据
        每次执行计数器+1,达到最大值后重置
        """
        # 1. 更新计数器
        self.count += 1
        if self.count > self.max_count:
            self.count = 0
            self.get_logger().info(f"计数器已重置,当前值: {self.count}")

        # 2. 发布String类型话题
        string_msg = String()
        string_msg.data = f"Hello ROS2! Count: {self.count}"
        self.string_pub.publish(string_msg)

        # 3. 发布Int32类型话题
        int_msg = Int32()
        int_msg.data = self.count
        self.int_pub.publish(int_msg)

        # 4. 日志输出(不同级别:debug/info/warn/error/fatal)
        self.get_logger().debug(f"发布数据: {string_msg.data}")  # 调试级(默认不显示,需设置日志级别)

    def sub_callback(self, msg):
        """
        话题订阅回调函数:接收订阅的话题数据并处理
        参数msg:接收到的消息对象(类型与订阅的话题一致)
        """
        self.get_logger().info(f"接收到话题数据: {msg.data}")
        # 可在此处添加数据处理逻辑(如解析、转发、控制等)

    def srv_callback(self, request, response):
        """
        服务端回调函数:处理客户端请求并返回响应
        参数:
            request: 客户端发送的请求数据
            response: 要返回给客户端的响应数据
        """
        if request.data:  # 如果请求的bool值为True
            self.count = 0  # 重置计数器
            response.success = True
            response.message = "计数器已重置为0"
            self.get_logger().info("服务请求:重置计数器成功")
        else:
            response.success = False
            response.message = "拒绝重置计数器"
            self.get_logger().warn("服务请求:拒绝重置计数器")
        return response

    def call_service_example(self):
        """
        服务客户端调用示例(可在需要时调用)
        """
        # 构造请求数据
        req = SetBool.Request()
        req.data = True  # 请求参数
        # 异步调用服务(非阻塞)
        future = self.srv_client.call_async(req)
        # 等待调用结果(阻塞,可选)
        rclpy.spin_until_future_complete(self, future)
        if future.result() is not None:
            self.get_logger().info(f"服务调用结果: {future.result().message}")
        else:
            self.get_logger().error("服务调用失败")

    def destroy_node(self):
        """
        节点销毁时的清理操作(可选,继承自Node类)
        用于释放资源、保存数据、打印结束日志等
        """
        self.get_logger().info("=== 自定义节点已关闭 ===")
        super().destroy_node()  # 必须调用父类的销毁方法

def main(args=None):
    """
    节点主函数:ROS2节点的入口
    """
    # 1. 初始化ROS2上下文
    rclpy.init(args=args)

    try:
        # 2. 创建节点实例
        node = MyFirstNode()

        # 3. 自旋节点(保持节点运行,处理回调)
        # 单线程执行器(默认)
        rclpy.spin(node)

    except KeyboardInterrupt:
        # 捕获Ctrl+C,优雅退出
        node.get_logger().info("接收到退出信号,正在关闭节点...")
    finally:
        # 4. 销毁节点,关闭ROS2上下文
        node.destroy_node()
        rclpy.shutdown()

# 程序入口(ROS2节点标准写法)
if __name__ == "__main__":
    main()
setup.py
复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ROS2 Python 功能包通用 setup.py 模板
支持:多节点配置、自定义msg/srv/action、数据文件(launch/rviz/config)部署、依赖管理、测试配置
"""

from setuptools import setup, find_packages
import os
from glob import glob

# ===================== 核心配置常量(需根据实际需求修改) =====================
PACKAGE_NAME = 'my_py_node'          # 功能包名称(必须与package.xml一致)
VERSION = '0.1.0'                    # 版本号(自定义,如0.1.0、1.0.0)
MAINTAINER = 'jzx'                   # 维护者姓名
MAINTAINER_EMAIL = 'jzx@todo.todo'   # 维护者邮箱
DESCRIPTION = 'ROS2 Python通用功能包模板,支持多节点、自定义消息/服务、数据文件部署'  # 功能包描述
LICENSE = 'Apache-2.0'               # 开源协议(常用:Apache-2.0、MIT、BSD)
PYTHON_REQUIRES = '>=3.8'            # Python版本要求(Humble对应3.8,Foxy对应3.8,Iron对应3.10)

# ===================== 自动推导配置(无需手动修改) =====================
# 1. 自动查找所有launch文件(用于部署到share目录)
launch_files = glob(os.path.join('launch', '*.launch.py'))
# 2. 自动查找所有配置文件(rviz、yaml等,用于部署到share目录)
config_files = glob(os.path.join('config', '*.yaml')) + glob(os.path.join('config', '*.rviz'))
# 3. 自动查找所有msg/srv/action文件(用于声明编译依赖)
msg_files = glob(os.path.join(PACKAGE_NAME, 'msg', '*.msg'))
srv_files = glob(os.path.join(PACKAGE_NAME, 'srv', '*.srv'))
action_files = glob(os.path.join(PACKAGE_NAME, 'action', '*.action'))

# ===================== 核心setup配置 =====================
setup(
    # 1. 基础信息(必须与package.xml一致)
    name=PACKAGE_NAME,
    version=VERSION,
    packages=find_packages(exclude=['test', 'docs']),  # 排除测试/文档目录
    data_files=[
        # 2. ROS2包索引文件(必须配置,否则ros2 pkg list找不到包)
        ('share/ament_index/resource_index/packages',
            ['resource/' + PACKAGE_NAME]),
        ('share/' + PACKAGE_NAME, ['package.xml']),
        
        # 3. 部署launch文件(自动查找,无需手动添加)
        ('share/' + PACKAGE_NAME + '/launch', launch_files),
        
        # 4. 部署配置文件(rviz/yaml等,自动查找)
        ('share/' + PACKAGE_NAME + '/config', config_files),
        
        # 5. 部署自定义msg/srv/action文件(供其他包依赖)
        ('share/' + PACKAGE_NAME + '/msg', [f for f in msg_files if os.path.exists(f)]),
        ('share/' + PACKAGE_NAME + '/srv', [f for f in srv_files if os.path.exists(f)]),
        ('share/' + PACKAGE_NAME + '/action', [f for f in action_files if os.path.exists(f)]),
        
        # 6. 可选:部署其他静态资源(如模型、地图、脚本等)
        # ('share/' + PACKAGE_NAME + '/models', glob('models/*')),
        # ('share/' + PACKAGE_NAME + '/maps', glob('maps/*.pgm') + glob('maps/*.yaml')),
    ],
    
    # 7. 安装依赖(ROS2核心依赖+Python第三方库)
    install_requires=[
        # ROS2核心依赖
        'setuptools>=50.3.0',  # 最低版本要求
        'rclpy>=3.0.0',        # ROS2 Python核心库
        'std_msgs',            # 标准消息类型
        'sensor_msgs',         # 传感器消息(如激光、图像)
        'geometry_msgs',       # 几何消息(如位姿、速度)
        'nav_msgs',            # 导航消息(如路径、里程计)
        'action_msgs',         # 动作消息(如需使用action)
        'rosidl_runtime_py',   # 自定义msg/srv编译依赖
        
        # Python第三方库(根据需求添加)
        'numpy>=1.21.0',       # 数值计算
        'pyserial>=3.5',       # 串口通信
        'opencv-python>=4.5.0',# 图像处理
        'pytest>=7.0.0',       # 测试框架(也可放extras_require)
    ],
    
    # 8. 兼容性配置(防止zip包导致的路径问题)
    zip_safe=True,
    
    # 9. 维护者信息(必须与package.xml一致)
    maintainer=MAINTAINER,
    maintainer_email=MAINTAINER_EMAIL,
    description=DESCRIPTION,
    license=LICENSE,
    python_requires=PYTHON_REQUIRES,
    
    # 10. 可选:测试依赖(仅在运行pytest时安装)
    extras_require={
        'test': [
            'pytest',          # 单元测试框架
            'pytest-cov',      # 测试覆盖率
            'pytest-asyncio',  # 异步测试(如需)
            'flake8',          # 代码风格检查
        ],
    },
    
    # 11. 节点入口配置(核心!ROS2 run 运行节点的关键)
    # 格式:"节点执行名 = 包名.文件名:主函数名"
    entry_points={
        'console_scripts': [
            # 示例1:基础节点(对应my_first_node.py)
            'my_first_node = my_py_node.my_first_node:main',
            
            # 示例2:话题发布节点(可新增)
            'topic_publisher = my_py_node.topic_publisher:main',
            
            # 示例3:话题订阅节点(可新增)
            'topic_subscriber = my_py_node.topic_subscriber:main',
            
            # 示例4:服务端节点(可新增)
            'service_server = my_py_node.service_server:main',
            
            # 示例5:客户端节点(可新增)
            'service_client = my_py_node.service_client:main',
            
            # 示例6:动作服务端/客户端(可新增)
            # 'action_server = my_py_node.action_server:main',
            # 'action_client = my_py_node.action_client:main',
        ],
    },
    
    # 12. 可选:控制台脚本元数据(非必须)
    classifiers=[
        'Intended Audience :: Developers',
        'License :: OSI Approved :: Apache Software License',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: 3.10',
        'Operating System :: Linux',
        'Topic :: Robotics',
    ],
)
package.xml
复制代码
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <!-- ===================== 核心基础信息(必须配置) ===================== -->
  <!-- 功能包名称:必须与setup.py中的PACKAGE_NAME一致,小写+下划线 -->
  <name>my_py_node</name>
  
  <!-- 版本号:遵循语义化版本(主版本.次版本.修订版),与setup.py一致 -->
  <version>0.1.0</version>
  
  <!-- 功能包描述:清晰说明包的用途 -->
  <description>
    ROS2 Python通用功能包模板,支持多节点、自定义消息/服务/动作、
    话题发布订阅、服务端客户端、定时器、参数管理等核心功能,
    适配机器人感知、控制、决策等场景开发。
  </description>
  
  <!-- 维护者信息:姓名+邮箱(必填,便于协作) -->
  <maintainer email="jzx@xxx.com">jzx</maintainer>
  
  <!-- 作者信息(可选,多人用多个<author>标签) -->
  <author email="dev1@xxx.com">Developer1</author>
  <author>Developer2</author>
  
  <!-- 许可证:常用Apache-2.0/MIT/BSD,与setup.py一致 -->
  <license>Apache-2.0</license>

  <!-- ===================== 依赖配置(核心!按场景分类) ===================== -->
  <!-- 1. 运行依赖:节点运行时必须的依赖(与setup.py的install_requires对应) -->
  <!-- ROS2核心依赖 -->
  <depend>rclpy</depend>                <!-- Python核心库 -->
  <depend>std_msgs</depend>             <!-- 标准消息类型(String/Int32等) -->
  <depend>sensor_msgs</depend>          <!-- 传感器消息(激光/图像/IMU等) -->
  <depend>geometry_msgs</depend>        <!-- 几何消息(位姿/速度/坐标系等) -->
  <depend>nav_msgs</depend>             <!-- 导航消息(路径/里程计/地图等) -->
  <depend>action_msgs</depend>          <!-- 动作消息(如需使用Action) -->
  <depend>rosidl_runtime_py</depend>    <!-- 自定义msg/srv编译运行依赖 -->
  
  <!-- 可选:其他常用依赖 -->
  <!-- <depend>tf2_ros</depend>           <!-- 坐标系变换 -->
  <!-- <depend>rviz2</depend>             <!-- 可视化工具 -->
  <!-- <depend>ament_index_python</depend> <!-- 索引查询 -->

  <!-- 2. 构建依赖:仅编译时需要,运行时不需要 -->
  <buildtool_depend>ament_python</buildtool_depend>  <!-- Python构建工具(必须) -->
  <build_depend>rosidl_default_generators</build_depend>  <!-- 自定义msg/srv编译生成器 -->

  <!-- 3. 执行依赖:编译+运行都需要(部分场景替代depend) -->
  <exec_depend>rosidl_default_runtime</exec_depend>  <!-- 自定义msg/srv运行时依赖 -->
  <exec_depend>python3-numpy</exec_depend>           <!-- Python第三方库(系统级) -->
  <!-- <exec_depend>python3-opencv</exec_depend>        <!-- OpenCV(系统级) -->

  <!-- 4. 自定义msg/srv/action必须的组声明 -->
  <member_of_group>rosidl_interface_packages</member_of_group>

  <!-- 5. 测试依赖:仅运行测试时需要 -->
  <test_depend>ament_copyright</test_depend>  <!-- 版权声明检查 -->
  <test_depend>ament_flake8</test_depend>     <!-- 代码风格检查(PEP8) -->
  <test_depend>ament_pep257</test_depend>    <!-- 文档字符串检查 -->
  <test_depend>python3-pytest</test_depend>  <!-- Python测试框架 -->
  <test_depend>ament_lint_auto</test_depend>  <!-- 自动触发所有lint检查 -->
  <test_depend>ament_lint_common</test_depend><!-- 通用lint配置 -->

  <!-- ===================== 构建类型与扩展配置 ===================== -->
  <!-- 构建类型:必须声明,与功能包类型一致(ament_python/Python,ament_cmake/C++) -->
  <export>
    <build_type>ament_python</build_type>
    
    <!-- 可选:标记为Lint自动检查(简化测试配置) -->
    <ament_lint_auto>true</ament_lint_auto>
    <!-- 可选:排除不需要的Lint检查(如需) -->
    <!-- <ament_lint_exclude>ament_copyright</ament_lint_exclude> -->
  </export>
</package>

2.节点学习

1. 节点的本质
  • 节点(Node)是 ROS2 中最小的可执行单元,一个机器人应用由多个独立节点组成(如 "激光雷达节点""运动控制节点""导航节点");
  • 每个节点有唯一名称,负责完成单一功能(模块化设计),节点间通过 "话题 / 服务 / 动作" 通信,互不干扰。
2. 节点核心特性
特性 说明 应用场景
话题(Topic) 异步通信(发布 / 订阅),一对多 / 多对多 传感器数据发布、状态广播
服务(Service) 同步通信(请求 / 响应),一对一 临时指令(如 "重置计数器""获取参数")
参数(Parameter) 节点的配置项,可动态修改 调整发布频率、设置阈值、配置路径
日志(Logger) 输出调试 / 运行信息(debug/info/warn/error/fatal) 排查问题、监控节点运行状态
生命周期 节点的启动 / 暂停 / 关闭等状态管理 复杂系统的节点状态控制(新手暂用不到)
3. 核心原则
  • 一个节点只做一件事(如 "只发布 TF 坐标""只处理激光数据");
  • 节点名必须唯一(避免冲突);
  • 通信依赖 "接口"(消息 / 服务类型),不同节点需使用相同接口才能通信。
4. 节点基础操作
复制代码
# 1. 运行节点(核心)
ros2 run <功能包名> <节点执行名>
# 示例:运行demo_python_tf包的static_tf_broadcaster节点
ros2 run demo_python_tf static_tf_broadcaster

# 2. 运行节点并修改参数
ros2 run demo_python_tf static_tf_broadcaster --ros-args -p publish_freq:=2.0

# 3. 查看当前运行的所有节点
ros2 node list

# 4. 查看节点详细信息(话题/服务/参数)
ros2 node info /static_tf_broadcaster

# 5. 终止节点(终端按Ctrl+C,或用kill命令)
kill -9 <节点进程ID>  # 先通过ps aux | grep ros2找到进程ID
5. 节点配套调试命令
复制代码
# 1. 查看话题列表(节点发布/订阅的话题)
ros2 topic list

# 2. 订阅并打印话题数据(调试发布节点)
ros2 topic echo /tf  # 查看TF坐标变换数据

# 3. 查看服务列表(节点提供的服务)
ros2 service list

# 4. 调用服务(调试服务节点)
ros2 service call /reset_count std_srvs/srv/SetBool "{data: true}"

# 5. 查看节点参数
ros2 param list  # 列出所有节点的参数
ros2 param get /static_tf_broadcaster publish_freq  # 获取指定参数值
ros2 param set /static_tf_broadcaster publish_freq 3.0  # 动态修改参数值

# 6. 查看日志(调试节点报错)
ros2 logger get /static_tf_broadcaster  # 查看日志级别
ros2 logger set /static_tf_broadcaster DEBUG  # 调整日志级别(显示更多调试信息)
6、 ROS2 节点极简模板

文件名:simple_node.py(放在功能包的 demo_python_tf/ 目录下)

复制代码
#!/usr/bin/env python3
"""ROS2 Python 极简节点模板:发布话题+日志输出"""
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class SimpleNode(Node):
    def __init__(self):
        # 1. 初始化节点(指定唯一名称)
        super().__init__("simple_node")
        
        # 2. 创建话题发布者(话题名:chatter,消息类型:String)
        self.publisher_ = self.create_publisher(String, "chatter", 10)
        
        # 3. 创建定时器(1秒执行1次回调)
        self.timer = self.create_timer(1.0, self.timer_callback)
        self.count = 0
        self.get_logger().info("极简节点已启动!")

    def timer_callback(self):
        # 发布话题数据
        msg = String()
        msg.data = f"Hello ROS2! Count: {self.count}"
        self.publisher_.publish(msg)
        self.get_logger().info(f"发布:{msg.data}")
        self.count += 1

def main(args=None):
    # 初始化ROS2
    rclpy.init(args=args)
    # 创建节点并运行
    node = SimpleNode()
    rclpy.spin(node)
    # 销毁节点
    node.destroy_node()
    rclpy.shutdown()

if __name__ == "__main__":
    main(

entry_points={
    'console_scripts': [
        'simple_node = demo_python_tf.simple_node:main',
    ],
},

3.话题

话题(Topic)是 ROS2 中异步、松耦合的通信机制,核心是 "发布者(Publisher)→ 话题 → 订阅者(Subscriber)" 的单向数据流模型:

  • 📤 发布者(Publisher) :主动发送数据的节点(如激光雷达节点发布 /scan 话题);
  • 📥 订阅者(Subscriber) :被动接收数据的节点(如导航节点订阅 /scan 话题);
  • 📨 话题 :数据传输的 "通道",有唯一名称(如 /chatter/tf/scan);
  • 📦 消息(Message) :话题传输的数据格式(如 std_msgs/msg/Stringsensor_msgs/msg/LaserScan),发布 / 订阅双方必须使用相同消息类型才能通信。
特性 说明 典型场景
异步通信 发布者发数据无需等待订阅者响应,订阅者按需接收 传感器数据(激光、图像、IMU)、状态广播
多对多 一个话题可被多个发布者发布、多个订阅者订阅 多个节点发布不同坐标系的 /tf 话题
松耦合 发布者和订阅者无需知道彼此存在,仅依赖话题名和消息类型 替换激光雷达节点,只要仍发布 /scan 话题,导航节点无需修改
1. 消息类型(Topic 的 "数据格式")
  • 标准消息:ROS2 内置的通用消息,如:
    • std_msgs/msg/String:字符串
    • std_msgs/msg/Int32:32 位整数
    • geometry_msgs/msg/Twist:速度指令(线速度 + 角速度)
    • sensor_msgs/msg/LaserScan:激光雷达数据
    • sensor_msgs/msg/Image:图像数据
  • 自定义消息:自己定义的消息格式(需在功能包中创建 .msg 文件并编译)。
2. 话题 QoS

QoS(Quality of Service)是话题的 "通信规则",用于适配不同场景(如实时性、可靠性):

  • 常用配置
    • RELIABLE:确保数据 100% 送达(默认,适合关键数据如控制指令);
    • BEST_EFFORT:尽力送达,不保证(适合高频数据如激光 / 图像,丢几个包不影响);
    • KEEP_LAST:只保留最新的 N 条数据(默认保留 10 条,避免内存溢出)。
  • 核心原则:发布者和订阅者的 QoS 配置需匹配,否则无法通信。
3. 话题命名规则
  • 必须以 / 开头(如 /chatter)或相对路径(如 chatter,会自动拼接节点命名空间);
  • 名称只能包含小写字母、数字、下划线、/
  • 避免重名(不同功能包的话题建议加前缀,如 /demo/chatter)。
4.话题常用命令(高频)
1. 基础查询命令
复制代码
# 1. 查看当前所有活跃话题(最常用)
ros2 topic list

# 2. 查看话题列表,同时显示消息类型(推荐)
ros2 topic list -t

# 3. 查看话题详细信息(发布者/订阅者、QoS、消息类型)
ros2 topic info /chatter

# 4. 查看消息类型的详细结构(知道话题数据包含哪些字段)
ros2 interface show std_msgs/msg/String  # 查看String消息结构
ros2 interface show geometry_msgs/msg/Twist  # 查看Twist消息结构
2. 数据调试命令
复制代码
# 1. 订阅并实时打印话题数据(调试发布者,核心命令)
ros2 topic echo /chatter  # 打印String类型话题
ros2 topic echo /cmd_vel  # 打印Twist类型话题

# 2. 只打印指定字段(避免数据太多,精准调试)
ros2 topic echo /cmd_vel linear.x  # 只打印线速度x轴的值

# 3. 限制打印频率(高频话题如/scan,避免刷屏)
ros2 topic echo /scan --rate 1  # 每秒只打印1次

# 4. 查看话题发布频率(判断发布者是否正常)
ros2 topic hz /chatter  # 显示每秒发布的消息数(Hz)

# 5. 查看话题数据延迟(调试实时性)
ros2 topic delay /chatter
3. 手动发布话题

无需写代码,直接在终端发布话题数据,快速验证订阅者逻辑:

复制代码
# 通用格式:ros2 topic pub <话题名> <消息类型> <数据>
ros2 topic pub /chatter std_msgs/msg/String "{data: 'Hello ROS2'}"

# 持续发布(按频率循环发布,调试运动控制常用)
ros2 topic pub --rate 1 /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.1}}"

# 一次性发布(默认只发1次)
ros2 topic pub --once /chatter std_msgs/msg/String "{data: 'One shot message'}"
4. 话题重映射

运行节点时,把节点原本的话题名映射成新名称,无需修改代码:

复制代码
# 示例:把节点发布的 /chatter 话题重映射为 /demo/chatter
ros2 run demo_python_tf simple_node --ros-args --remap chatter:=demo/chatter

# 同时重映射多个话题
ros2 run demo_python_tf simple_node --ros-args --remap chatter:=demo/chatter --remap cmd_vel:=demo/cmd_vel
5. 话题录制 / 回放
复制代码
# 1. 录制指定话题到bag文件(保存数据)
ros2 bag record /chatter /cmd_vel  # 录制/chatter和/cmd_vel到bag文件
ros2 bag record -o my_bag /chatter  # 指定输出文件名my_bag

# 2. 录制所有话题
ros2 bag record -a

# 3. 回放bag文件(复现数据)
ros2 bag play my_bag_2026_01_25-10_00_00/  # 替换为实际bag目录名
5.示例
复制代码
# 激活环境
source install/setup.bash
# 运行发布节点(发布/chatter话题)
ros2 run demo_python_tf simple_node

# 查看话题列表(能看到/chatter)
ros2 topic list -t
# 输出:/chatter [std_msgs/msg/String]

# 查看话题详细信息
ros2 topic info /chatter
# 输出:发布者1个,订阅者0个,消息类型std_msgs/msg/String

ros2 topic echo /chatter
# 能看到实时打印:data: "Hello ROS2! Count: 0" 等内容

# 新开终端,手动发布/chatter话题
ros2 topic pub --rate 2 /chatter std_msgs/msg/String "{data: 'Manual publish!'}"
# 此时步骤3的终端会同时收到节点发布的和手动发布的消息
发布者
复制代码
#!/usr/bin/env python3
"""ROS2 Python 极简话题发布模板"""
import rclpy
from rclpy.node import Node
# 导入标准字符串消息类型(可替换为其他类型如Int32/Twist)
from std_msgs.msg import String

class SimplePublisher(Node):
    def __init__(self):
        # 1. 初始化节点(命名唯一)
        super().__init__("simple_publisher_node")
        
        # 2. 创建话题发布者
        # 参数说明:话题名、消息类型、队列大小(QoS保留最新10条)
        self.publisher_ = self.create_publisher(String, "chatter", 10)
        
        # 3. 创建定时器(1秒发布1次,周期可调)
        self.timer = self.create_timer(1.0, self.timer_callback)
        self.count = 0  # 计数器,用于区分每次发布的消息
        self.get_logger().info("话题发布节点已启动!")

    def timer_callback(self):
        """定时器回调函数:周期性发布话题数据"""
        # 4. 构造消息数据
        msg = String()
        msg.data = f"Hello ROS2! Count: {self.count}"
        
        # 5. 发布消息
        self.publisher_.publish(msg)
        
        # 6. 日志输出(可选,方便调试)
        self.get_logger().info(f"发布消息:{msg.data}")
        self.count += 1

def main(args=None):
    # 初始化ROS2上下文
    rclpy.init(args=args)
    # 创建发布节点实例
    node = SimplePublisher()
    # 自旋节点(保持节点运行,处理定时器回调)
    rclpy.spin(node)
    # 销毁节点+关闭ROS2
    node.destroy_node()
    rclpy.shutdown()

if __name__ == "__main__":
    main()
消费者
复制代码
#!/usr/bin/env python3
"""ROS2 Python 极简话题订阅模板(消费者)"""
import rclpy
from rclpy.node import Node
# 导入和发布者一致的消息类型(必须匹配!)
from std_msgs.msg import String

class SimpleSubscriber(Node):
    def __init__(self):
        # 1. 初始化节点(命名唯一,不能和发布者节点重名)
        super().__init__("simple_subscriber_node")
        
        # 2. 创建话题订阅者
        # 参数说明:话题名(和发布者一致)、消息类型(和发布者一致)、回调函数、队列大小
        self.subscription = self.create_subscription(
            String,
            "chatter",  # 必须和发布者的话题名完全一致
            self.listener_callback,  # 收到消息后执行的回调函数
            10  # QoS队列大小,和发布者匹配即可
        )
        # 防止变量被垃圾回收(可选,但建议加)
        self.subscription  
        self.get_logger().info("话题订阅节点已启动!等待接收消息...")

    def listener_callback(self, msg):
        """订阅回调函数:收到消息时自动执行"""
        # 3. 处理接收到的消息(核心逻辑)
        self.get_logger().info(f"收到消息:{msg.data}")

def main(args=None):
    # 初始化ROS2上下文
    rclpy.init(args=args)
    # 创建订阅节点实例
    node = SimpleSubscriber()
    # 自旋节点(保持节点运行,等待接收消息)
    rclpy.spin(node)
    # 销毁节点+关闭ROS2
    node.destroy_node()
    rclpy.shutdown()

if __name__ == "__main__":
    main()
6.总结
核心知识点
  1. 话题是异步松耦合 通信机制,核心是 "发布者 - 话题 - 订阅者" 模型,依赖统一的消息类型
  2. QoS 是话题的通信规则,发布 / 订阅双方需匹配才能通信;
  3. 话题命名需规范,避免重名,高频数据建议用 BEST_EFFORT QoS。
高频命令

查:ros2 topic list -t(看话题 + 类型)、ros2 topic info(看详情);

调:ros2 topic echo(看数据)、ros2 topic hz(看频率);

测:ros2 topic pub(手动发数据)、--remap(重映射话题名)。

4.服务

服务核心认知

服务(Service)是 ROS2 中同步、请求 - 响应式的通信机制,核心是 "客户端(Client)→ 服务端(Server)" 的双向交互模型,和话题的异步单向通信形成互补:

  • 📞 服务端(Server):提供服务的节点(如 "提供重置计数器服务" 的节点),被动等待客户端请求,处理后返回响应;
  • 📱 客户端(Client):发起请求的节点(如 "请求重置计数器" 的节点),主动发送请求,阻塞等待响应;
  • 📋 服务 :交互的 "接口",有唯一名称(如 /reset_count);
  • 📄 服务类型(srv):定义请求(Request)和响应(Response)的数据格式(如 "请求:是否重置;响应:是否成功 + 提示信息")。
服务 vs 话题
特性 服务(Service) 话题(Topic)
通信模式 同步、请求 - 响应(双向) 异步、发布 - 订阅(单向)
交互时机 按需触发(如 "重置指令") 周期性推送(如 "传感器数据")
阻塞性 客户端会阻塞等待响应 发布 / 订阅均不阻塞
适用场景 指令下发、参数查询、任务触发 数据广播、状态监控
示例 重置计数器、获取节点状态 发布激光数据、速度指令
服务知识点
服务类型(srv)的结构

服务类型文件(.srv)分为 "请求" 和 "响应" 两部分,用 --- 分隔:

  • 标准服务类型

    (ROS2 内置):

    • std_srvs/srv/SetBool:请求(bool data)→ 响应(bool success + string message);
    • std_srvs/srv/Empty:无请求 → 无响应(仅触发动作);
    • rcl_interfaces/srv/SetParameters:设置节点参数的服务类型;
  • 自定义服务类型 :自己创建 .srv 文件,定义请求和响应格式(需在功能包中编译)。

标准服务类型示例

std_srvs/srv/SetBool

复制代码
# 请求部分(客户端发给服务端)
bool data  # 比如:true=重置计数器,false=不重置
---
# 响应部分(服务端返回给客户端)
bool success  # 处理是否成功
string message  # 提示信息(如"重置成功")
服务的核心特性
  • 一对一交互:一个服务请求只能对应一个服务端处理(服务名唯一,避免多个服务端重名);
  • 阻塞性:客户端发送请求后,会阻塞等待服务端响应(可设置超时时间,避免卡死);
  • 无缓存:服务请求 / 响应仅一次有效,不会缓存历史请求;
  • 命名规则 :和话题一致,以 / 开头(如 /reset_count),仅包含小写字母、数字、下划线、/
服务的 QoS

服务的 QoS 比话题简单,核心是 "可靠传输(RELIABLE)",确保请求和响应 100% 送达(ROS2 默认配置,新手无需修改)。

服务常用命令
1. 基础查询命令
复制代码
# 1. 查看当前所有活跃服务(最常用)
ros2 service list

# 2. 查看服务列表,同时显示服务类型(推荐)
ros2 service list -t

# 3. 查看服务详细信息(服务端节点、服务类型)
ros2 service info /reset_count

# 4. 查看服务类型的结构(请求+响应字段)
ros2 interface show std_srvs/srv/SetBool  # 查看SetBool的请求/响应格式
ros2 interface show std_srvs/srv/Empty    # 查看Empty的结构
2. 手动调用服务

无需写代码,直接在终端调用服务,快速验证服务端逻辑:

复制代码
# 通用格式:ros2 service call <服务名> <服务类型> <请求数据>
# 示例1:调用SetBool类型服务(/reset_count)
ros2 service call /reset_count std_srvs/srv/SetBool "{data: true}"

# 示例2:调用Empty类型服务(无请求数据)
ros2 service call /empty_service std_srvs/srv/Empty "{}"

# 示例3:带超时调用(避免等待过久,单位:秒)
ros2 service call /reset_count std_srvs/srv/SetBool "{data: true}" --timeout 5
3. 服务重映射

运行节点时,把节点原本的服务名映射成新名称,无需修改代码:

复制代码
# 示例:把服务端的 /reset_count 重映射为 /demo/reset_count
ros2 run 你的包名 service_server --ros-args --remap reset_count:=demo/reset_count

# 客户端调用时需用新服务名
ros2 service call /demo/reset_count std_srvs/srv/SetBool "{data: true}"
4. 服务端 / 客户端调试辅助命令
复制代码
# 1. 查看节点提供的服务(确认服务端是否正确注册服务)
ros2 node info /service_server_node

# 2. 检查服务是否可用(返回true/false)
ros2 service find std_srvs/srv/SetBool  # 查找所有SetBool类型的服务

# 3. 查看服务通信的日志(调试服务调用失败)
ros2 logger set /service_client_node DEBUG  # 调高客户端日志级别
示例
步骤 1:运行服务端节点
复制代码
# 激活环境
source install/setup.bash
# 运行提供/reset_count服务的节点
ros2 run demo_py_node service_server
# 终端输出:服务端已启动,等待请求...
步骤 2:查看服务信息
复制代码
# 查看服务列表(能看到/reset_count)
ros2 service list -t
# 输出:/reset_count [std_srvs/srv/SetBool]

# 查看服务类型结构
ros2 interface show std_srvs/srv/SetBool
# 输出:
# bool data
# ---
# bool success
# string message
步骤 3:手动调用服务
复制代码
# 调用/reset_count服务,请求重置计数器
ros2 service call /reset_count std_srvs/srv/SetBool "{data: true}"
# 终端返回响应(示例):
# requester: making request: std_srvs.srv.SetBool_Request(data=True)
# response:
# std_srvs.srv.SetBool_Response(success=True, message='计数器已重置为0')
服务端代码
复制代码
#!/usr/bin/env python3
"""ROS2 Python 服务端极简模板(提供重置计数器服务)"""
import rclpy
from rclpy.node import Node
# 导入标准服务类型(SetBool:请求bool,响应success+message)
from std_srvs.srv import SetBool

class SimpleServiceServer(Node):
    def __init__(self):
        # 1. 初始化节点(命名唯一)
        super().__init__("simple_service_server_node")
        
        # 2. 创建服务端
        # 参数说明:服务名、服务类型、回调函数(处理请求)
        self.service = self.create_service(
            SetBool,          # 服务类型(SetBool)
            "reset_count",    # 服务名(客户端需用此名称调用)
            self.reset_count_callback  # 处理请求的回调函数
        )
        
        # 初始化计数器(服务端的核心数据)
        self.count = 0
        self.get_logger().info("服务端已启动!等待重置计数器请求...")

    def reset_count_callback(self, request, response):
        """服务回调函数:处理客户端请求,返回响应"""
        # 3. 处理请求(request.data 是客户端传入的bool值)
        if request.data:
            self.count = 0  # 重置计数器
            response.success = True
            response.message = f"计数器已重置为0"
            self.get_logger().info(f"收到重置请求,计数器已重置:{self.count}")
        else:
            response.success = False
            response.message = f"拒绝重置,当前计数器值:{self.count}"
            self.get_logger().info(f"收到拒绝重置请求,当前值:{self.count}")
        
        # 4. 返回响应(必须返回response对象)
        return response

def main(args=None):
    # 初始化ROS2
    rclpy.init(args=args)
    # 创建服务端节点
    server_node = SimpleServiceServer()
    # 自旋节点(保持运行,等待请求)
    rclpy.spin(server_node)
    # 销毁节点+关闭ROS2
    server_node.destroy_node()
    rclpy.shutdown()

if __name__ == "__main__":
    main()
客户端代码
复制代码
#!/usr/bin/env python3
"""ROS2 Python 客户端极简模板(调用重置计数器服务)"""
import rclpy
from rclpy.node import Node
# 导入和服务端一致的服务类型
from std_srvs.srv import SetBool

class SimpleServiceClient(Node):
    def __init__(self):
        # 1. 初始化节点
        super().__init__("simple_service_client_node")
        
        # 2. 创建客户端
        # 参数说明:服务名(和服务端一致)、服务类型
        self.client = self.create_client(SetBool, "reset_count")
        
        # 3. 等待服务端上线(避免调用时服务未就绪)
        while not self.client.wait_for_service(timeout_sec=1.0):
            self.get_logger().warn("服务端未上线,等待中...")
        
        self.get_logger().info("服务端已上线,可发送请求!")

    def send_request(self, reset_flag):
        """发送服务请求(封装成函数,方便调用)"""
        # 4. 构造请求数据
        request = SetBool.Request()
        request.data = reset_flag  # True=重置,False=不重置
        
        # 5. 发送请求(异步调用,非阻塞;也可用call()同步阻塞)
        self.future = self.client.call_async(request)
        # 等待请求处理完成(阻塞直到收到响应)
        rclpy.spin_until_future_complete(self, self.future)
        
        # 6. 处理响应
        if self.future.result() is not None:
            response = self.future.result()
            self.get_logger().info(f"服务调用结果:success={response.success},message={response.message}")
        else:
            self.get_logger().error("服务调用失败!")

def main(args=None):
    rclpy.init(args=args)
    # 创建客户端节点
    client_node = SimpleServiceClient()
    # 发送重置请求(True=重置,False=不重置)
    client_node.send_request(reset_flag=True)
    # 销毁节点+关闭ROS2
    client_node.destroy_node()
    rclpy.shutdown()

if __name__ == "__main__":
    main()
总结
核心知识点
  1. 服务是同步请求 - 响应通信,适用于按需触发的指令交互,和话题的异步广播形成互补;
  2. 服务类型(srv)定义 "请求" 和 "响应" 格式,用 --- 分隔,客户端和服务端必须使用相同类型;
  3. 服务名唯一,一个服务只能有一个服务端,客户端调用时会阻塞等待响应。
高频命令

查:ros2 service list -t(看服务 + 类型)、ros2 interface show(看请求 / 响应结构);

调:ros2 service call(手动调用服务,验证服务端逻辑);

排错:ros2 service info(看服务端信息)、--timeout(避免客户端卡死)。

5.动作

动作(Action)是 ROS2 为长时间任务设计的通信机制,核心是 "动作客户端(Action Client)→ 动作服务端(Action Server)" 的双向交互模型,兼具话题的异步性和服务的请求 - 响应特性,还增加了 "反馈" 和 "取消" 能力:

  • 🎯 动作服务端:执行长时间任务的节点(如 "导航到目标点""机械臂运动"),能实时反馈任务进度,支持接收取消指令;
  • 📱 动作客户端:发起任务请求的节点,可发送目标、接收实时反馈、获取最终结果,也能中途取消任务;
  • 📋 动作 :交互的 "接口",有唯一名称(如 /navigate_to_pose);
  • 📄 动作类型(action):定义 "目标(Goal)、反馈(Feedback)、结果(Result)" 三部分数据格式,是动作的核心约定。
动作 vs 话题 vs 服务
特性 动作(Action) 话题(Topic) 服务(Service)
通信模式 异步、目标 - 反馈 - 结果(双向) 异步、发布 - 订阅(单向) 同步、请求 - 响应(双向)
任务时长 长时间任务(如导航、运动) 无时长限制(高频数据) 短时间任务(单次指令)
核心能力 实时反馈、任务取消、结果返回 数据广播 单次响应
阻塞性 客户端非阻塞(反馈异步接收) 无阻塞 客户端阻塞
适用场景 导航、机械臂运动、路径规划 传感器数据、状态广播 重置计数器、参数查询
动作关键知识点
1. 动作类型(action)的结构

动作类型文件(.action)分为三部分,用 --- 分隔,是动作最核心的约定:

复制代码
# 1. 目标(Goal):客户端发给服务端的任务指令
int32 target_count  # 示例:计数到指定值
---
# 2. 反馈(Feedback):服务端实时返回的进度
int32 current_count  # 示例:当前计数
---
# 3. 结果(Result):任务完成/取消后返回的最终结果
bool success         # 示例:是否成功
string message       # 示例:提示信息
int32 total_count    # 示例:最终计数
  • 标准动作类型

    (ROS2 内置):

    • action_tutorials_interfaces/action/Fibonacci:斐波那契数列计算(官方示例);
    • nav2_msgs/action/NavigateToPose:导航到指定位姿(导航 2 框架核心动作);
  • 自定义动作类型 :自己创建 .action 文件,定义 Goal/Feedback/Result,需在功能包中编译。

2. 动作的核心特性
  • 异步非阻塞:客户端发送目标后无需等待,可继续执行其他逻辑,反馈和结果异步接收;
  • 实时反馈:服务端可周期性返回任务进度(如导航的 "剩余距离""当前位姿");
  • 任务取消:客户端可中途取消任务,服务端收到取消指令后停止任务并返回结果;
  • 目标优先级:支持多个目标排队 / 覆盖(如导航时重新发送目标点,取消原任务);
  • 命名规则 :和话题 / 服务一致,以 / 开头(如 /navigate),仅包含小写字母、数字、下划线、/
3. 动作的 QoS

动作底层基于话题实现,QoS 默认配置为 "可靠传输(RELIABLE)+ 保留最新数据(KEEP_LAST)",确保目标、反馈、结果不丢失,新手无需手动修改。

动作常用命令
1. 基础查询命令
复制代码
# 1. 查看当前所有活跃动作(最常用)
ros2 action list

# 2. 查看动作列表,同时显示动作类型(推荐)
ros2 action list -t

# 3. 查看动作详细信息(服务端节点、动作类型、目标状态)
ros2 action info /fibonacci

# 4. 查看动作类型的结构(Goal/Feedback/Result)
ros2 interface show action_tutorials_interfaces/action/Fibonacci
2. 手动发送动作目标

无需写代码,直接在终端发送动作目标,接收反馈和结果,快速验证动作服务端逻辑:

复制代码
# 通用格式:ros2 action send_goal <动作名> <动作类型> <目标数据> [--feedback]
# 示例1:调用斐波那契数列动作,开启实时反馈
ros2 action send_goal /fibonacci action_tutorials_interfaces/action/Fibonacci "{order: 5}" --feedback

# 示例2:发送目标后中途取消(按Ctrl+C即可触发取消指令)
ros2 action send_goal /count_action demo_interfaces/action/Count "{target_count: 10}" --feedback

# 示例3:仅获取最终结果(不显示反馈)
ros2 action send_goal /count_action demo_interfaces/action/Count "{target_count: 5}"
3. 动作目标管理命令
复制代码
# 1. 查看当前所有动作目标的状态(pending/active/canceled/succeeded)
ros2 action list -s

# 2. 取消指定动作的当前目标(需知道目标ID,新手暂用不到)
ros2 action cancel /fibonacci <goal_id>

# 3. 查找指定类型的所有动作
ros2 action find action_tutorials_interfaces/action/Fibonacci
示例
步骤 1:运行官方动作示例

ROS2 内置了斐波那契数列动作示例,无需自己写代码,直接运行:

复制代码
# 激活环境
source /opt/ros/humble/setup.bash

# 终端1:运行动作服务端
ros2 run action_tutorials_cpp fibonacci_action_server

# 终端2:运行动作客户端(或用命令手动调用)
ros2 run action_tutorials_cpp fibonacci_action_client
# 或手动发送目标(推荐,能看到反馈)
ros2 action send_goal /fibonacci action_tutorials_interfaces/action/Fibonacci "{order: 8}" --feedback
步骤 2:查看执行效果

手动调用后终端会输出:

复制代码
# 目标发送成功
Waiting for an action server to become available...
Sending goal:
   order: 8

# 实时反馈(--feedback 参数生效)
Feedback:
   sequence: [0, 1]
Feedback:
   sequence: [0, 1, 1]
...

# 最终结果
Result:
   sequence: [0, 1, 1, 2, 3, 5, 8, 13, 21]

Goal finished with status: SUCCEEDED
代码
第一步:先创建自定义动作类型

动作依赖 .action 文件定义数据格式,需先在功能包中创建:

1. 创建动作文件目录与文件

在你的 Python 功能包(如 demo_py_node)下新建目录和文件:

复制代码
# 进入功能包目录
cd ~/ros2_ws/src/demo_py_node
# 创建action目录
mkdir action
# 创建计数动作文件
touch action/Count.action
2. 编写 Count.action 文件内容

定义「目标(计数到多少)、反馈(当前数到多少)、结果(是否成功)」:

复制代码
# Goal:客户端发送的目标(计数到target_count)
int32 target_count
---
# Feedback:服务端实时反馈的进度
int32 current_count
---
# Result:任务完成/取消后的最终结果
bool success
string message
int32 total_count
3. 修改 package.xml(添加动作编译依赖)

确保 package.xml 包含以下依赖(适配自定义动作):

复制代码
<!-- 动作编译核心依赖 -->
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
<!-- 动作运行依赖 -->
<depend>rclpy</depend>
<depend>action_msgs</depend>
4. 修改 setup.py(配置动作文件部署)

setup.py 中添加动作文件的部署逻辑(自动查找 .action 文件):

复制代码
from glob import glob
import os

# 自动查找action文件
action_files = glob(os.path.join('action', '*.action'))

setup(
    # 其他配置不变,在data_files中添加:
    data_files=[
        # 原有配置...
        ('share/' + PACKAGE_NAME + '/action', action_files),
    ],
    # 安装依赖新增action相关
    install_requires=[
        'setuptools>=50.3.0',
        'rclpy>=3.0.0',
        'rosidl_runtime_py',
        'action_msgs',
    ],
)
第二步:动作服务端代码(count_action_server.py)

负责执行「计数任务」,实时反馈进度,支持取消:

复制代码
#!/usr/bin/env python3
"""ROS2 Python 动作服务端示例:计数到指定值"""
import rclpy
from rclpy.action import ActionServer
from rclpy.node import Node
# 导入自定义动作类型(生成的模块名=包名.action.动作文件名)
from demo_py_node.action import Count

class CountActionServer(Node):
    def __init__(self):
        super().__init__("count_action_server_node")
        # 1. 创建动作服务端
        self._action_server = ActionServer(
            self,
            Count,          # 动作类型
            "count_action", # 动作名(客户端需用此名称调用)
            self.execute_callback  # 执行任务的回调函数
        )
        self.get_logger().info("计数动作服务端已启动!等待计数请求...")

    def execute_callback(self, goal_handle):
        """执行动作任务的核心回调:计数到目标值,实时反馈进度"""
        self.get_logger().info(f"开始计数任务,目标值:{goal_handle.request.target_count}")
        
        # 初始化进度
        feedback_msg = Count.Feedback()
        current_count = 0

        # 循环计数(每秒数1个)
        while current_count < goal_handle.request.target_count:
            # 检查是否收到取消请求
            if goal_handle.is_cancel_requested:
                goal_handle.canceled()
                self.get_logger().info("计数任务被取消!")
                # 返回取消结果
                result = Count.Result()
                result.success = False
                result.message = "任务被取消"
                result.total_count = current_count
                return result

            # 实时更新反馈
            current_count += 1
            feedback_msg.current_count = current_count
            self.get_logger().info(f"当前计数:{current_count}")
            goal_handle.publish_feedback(feedback_msg)

            # 等待1秒(模拟耗时任务)
            rclpy.spin_once(self, timeout_sec=1.0)

        # 任务完成,标记为成功
        goal_handle.succeed()
        self.get_logger().info("计数任务完成!")
        
        # 构造最终结果
        result = Count.Result()
        result.success = True
        result.message = f"成功计数到 {current_count}"
        result.total_count = current_count

        return result

def main(args=None):
    rclpy.init(args=args)
    server_node = CountActionServer()
    rclpy.spin(server_node)
    server_node.destroy_node()
    rclpy.shutdown()

if __name__ == "__main__":
    main()
第三步:动作客户端代码(count_action_client.py)

负责发起计数请求,接收实时反馈,支持取消任务:

复制代码
#!/usr/bin/env python3
"""ROS2 Python 动作客户端示例:发起计数请求"""
import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node
# 导入自定义动作类型
from demo_py_node.action import Count

class CountActionClient(Node):
    def __init__(self):
        super().__init__("count_action_client_node")
        # 1. 创建动作客户端
        self._action_client = ActionClient(self, Count, "count_action")
        self.get_logger().info("计数动作客户端已启动!")

    def send_goal(self, target_count):
        """发送计数目标,并处理反馈和结果"""
        # 构造目标请求
        goal_msg = Count.Goal()
        goal_msg.target_count = target_count

        # 2. 等待服务端上线
        self.get_logger().info("等待动作服务端上线...")
        self._action_client.wait_for_server()

        # 3. 发送目标(异步),绑定反馈和结果回调
        self._send_goal_future = self._action_client.send_goal_async(
            goal_msg,
            feedback_callback=self.feedback_callback  # 实时反馈回调
        )

        # 4. 等待目标发送结果,并绑定任务完成回调
        self._send_goal_future.add_done_callback(self.goal_response_callback)

    def goal_response_callback(self, future):
        """目标发送结果回调:确认服务端是否接受目标"""
        goal_handle = future.result()
        if not goal_handle.accepted:
            self.get_logger().error("计数请求被服务端拒绝!")
            return

        self.get_logger().info("计数请求已被接受,等待任务完成...")

        # 5. 等待任务完成,获取最终结果
        self._get_result_future = goal_handle.get_result_async()
        self._get_result_future.add_done_callback(self.get_result_callback)

        # 可选:模拟5秒后取消任务(注释掉则完成全部计数)
        # self.create_timer(5.0, lambda: goal_handle.cancel_goal_async())

    def feedback_callback(self, feedback_msg):
        """实时反馈回调:接收服务端的进度"""
        feedback = feedback_msg.feedback
        self.get_logger().info(f"收到进度反馈:当前计数={feedback.current_count}")

    def get_result_callback(self, future):
        """最终结果回调:处理任务完成/取消的结果"""
        result = future.result().result
        status = future.result().status
        if status == 3:  # SUCCEEDED
            self.get_logger().info(f"任务成功!结果:{result.message},总计数:{result.total_count}")
        elif status == 2:  # CANCELED
            self.get_logger().info(f"任务被取消!已计数:{result.total_count}")
        else:
            self.get_logger().error(f"任务失败!状态码:{status}")

        # 任务完成后关闭节点
        rclpy.shutdown()

def main(args=None):
    rclpy.init(args=args)
    client_node = CountActionClient()
    # 发送计数请求(目标数到10)
    client_node.send_goal(target_count=10)
    # 自旋节点,等待回调执行
    rclpy.spin(client_node)
    client_node.destroy_node()

if __name__ == "__main__":
    main()
第四步:配置节点入口与运行
1. 修改 setup.py 的 entry_points

添加动作服务端 / 客户端的运行入口:

复制代码
entry_points={
    'console_scripts': [
        # 动作服务端
        'count_action_server = demo_py_node.count_action_server:main',
        # 动作客户端
        'count_action_client = demo_py_node.count_action_client:main',
    ],
},
2. 编译与运行
复制代码
# 1. 编译功能包(首次编译自定义动作需完整编译)
cd ~/ros2_ws
colcon build --packages-select demo_py_node --symlink-install
source install/setup.bash

# 2. 终端1:运行动作服务端
ros2 run demo_py_node count_action_server

# 3. 终端2:运行动作客户端
ros2 run demo_py_node count_action_client

# 可选:手动发送动作目标(验证服务端)
ros2 action send_goal /count_action demo_py_node/action/Count "{target_count: 5}" --feedback
总结
核心知识点
  1. 动作是为长时间任务 设计的通信机制,核心能力是实时反馈、任务取消、异步结果返回
  2. 动作类型(.action)包含 Goal(目标)、Feedback(反馈)、Result(结果)三部分,客户端和服务端必须使用相同类型;
  3. 动作兼具话题的异步性和服务的交互性,是导航、机械臂运动等场景的首选通信方式。
高频命令

查:ros2 action list -t(看动作 + 类型)、ros2 interface show(看 Goal/Feedback/Result 结构);

调:ros2 action send_goal <动作名> <类型> <目标> --feedback(手动发送目标,查看反馈);

排错:ros2 action info(看服务端信息)、ros2 action list -s(看目标状态)。

6.launch

Launch 核心认知

Launch(启动文件)是 ROS2 为复杂系统启动设计的工具,核心作用是:

  • 🚀 批量启动多个节点(如同时启动 "导航节点 + 激光节点 + 控制节点");
  • ⚙️ 统一配置节点参数、话题重映射、命名空间;
  • 🧩 管理节点依赖(如 "先启动地图节点,再启动导航节点");
  • 📌 动态控制节点生命周期(如 "某个节点崩溃后自动重启")。
Launch 优势
手动启动节点 Launch 启动节点
需打开多个终端,逐个运行 ros2 run 一个终端运行 ros2 launch,批量启动所有节点
每次启动需重复输入参数 / 重映射指令 参数 / 重映射配置写在 Launch 文件中,一次编写永久复用
无依赖管理,节点启动顺序混乱 可指定节点启动顺序,避免 "依赖节点未启动导致报错"
Launch 关键知识点
1. Launch 文件的类型
类型 后缀 特点 适用场景
Python 版 .launch.py 灵活、支持逻辑控制(if/for)、ROS2 官方推荐 绝大多数场景(新手首选)
XML 版 .launch.xml 结构化强、语法简洁 简单场景(无复杂逻辑)
YAML 版 .launch.yaml 配置化、易读 纯节点启动(无逻辑)
2. Launch 的核心组件

Launch 文件的核心是通过 "动作(Action)" 和 "描述器(Descriptor)" 定义启动逻辑,常用组件:

  • Node:启动单个 ROS2 节点(核心组件);
  • Parameter:为节点设置参数;
  • Remap:为节点做话题 / 服务重映射;
  • Namespace:为节点设置命名空间(避免节点名 / 话题名冲突);
  • IncludeLaunchDescription:嵌套引入其他 Launch 文件(模块化);
  • SetEnvironmentVariable:设置环境变量(如日志级别);
  • LaunchConfiguration:接收命令行传入的参数(动态配置)。
3. Launch 文件的部署规则
  • Launch 文件需放在功能包的 launch/ 目录下;
  • 编译时需在 setup.py(Python 包)或 CMakeLists.txt(C++ 包)中配置,将 launch/ 目录安装到 install/包名/share/包名/launch/ 路径;
  • 运行时通过 ros2 launch 包名 文件名.launch.py 调用,无需指定路径。
4. 节点启动策略
  • respawn=True:节点崩溃后自动重启(如关键传感器节点);
  • respawn_delay=5:重启延迟 5 秒(避免频繁重启);
  • output='screen':节点日志输出到终端(调试必备);
  • arguments:给节点传递命令行参数(如 --ros-args -p freq:=2.0)。
Python 版 Launch 编写模板

以下是覆盖 "多节点启动、参数配置、话题重映射、命名空间、命令行传参" 的通用模板,基于之前的 "话题发布 / 订阅、服务、动作" 节点编写:

1. 基础模板

demo_launch.py

放在功能包的 launch/ 目录下:

复制代码
#!/usr/bin/env python3
"""ROS2 Python Launch 通用模板:批量启动多节点"""
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration, TextSubstitution

def generate_launch_description():
    # 1. 声明命令行参数(可选,动态配置)
    count_arg = DeclareLaunchArgument(
        'target_count',  # 参数名
        default_value=TextSubstitution(text='10'),  # 默认值
        description='计数动作的目标值'  # 描述
    )

    # 2. 定义节点1:话题发布节点
    publisher_node = Node(
        package='demo_py_node',  # 功能包名
        executable='topic_publisher',  # 节点执行名(setup.py中定义的)
        name='publisher_node',  # 节点名(覆盖代码中的节点名)
        namespace='demo',  # 命名空间(话题会变成 /demo/chatter)
        output='screen',  # 日志输出到终端
        respawn=True,  # 崩溃自动重启
        respawn_delay=3,  # 重启延迟3秒
        # 话题重映射(将节点的 /chatter 重映射为 /demo/chat)
        remappings=[
            ('chatter', 'chat')
        ],
        # 节点参数(等价于 --ros-args -p publish_freq:=2.0)
        parameters=[{
            'publish_freq': 2.0,
            'max_count': 100
        }]
    )

    # 3. 定义节点2:话题订阅节点
    subscriber_node = Node(
        package='demo_py_node',
        executable='topic_subscriber',
        name='subscriber_node',
        namespace='demo',
        output='screen',
        remappings=[
            ('chatter', 'chat')  # 和发布者重映射一致
        ]
    )

    # 4. 定义节点3:动作服务端节点(使用命令行参数)
    action_server_node = Node(
        package='demo_py_node',
        executable='count_action_server',
        name='count_action_server_node',
        output='screen',
        # 传递命令行参数到节点(可选)
        arguments=['--ros-args', '-p', f'target_count:={LaunchConfiguration("target_count")}']
    )

    # 5. 组装所有启动动作
    return LaunchDescription([
        count_arg,  # 先声明命令行参数
        publisher_node,
        subscriber_node,
        action_server_node
        # 可添加更多节点...
    ])
2. 模板关键组件说明
组件 作用 示例
DeclareLaunchArgument 声明命令行参数,支持动态配置 target_count 参数,默认值 10
Node(package/executable) 指定要启动的节点(功能包 + 执行名) package='demo_py_node', executable='topic_publisher'
name 覆盖节点代码中的默认节点名,避免冲突 name='publisher_node'
namespace 给节点加命名空间,话题变为 /命名空间/话题名 namespace='demo' → 话题 /demo/chatter
remappings 话题 / 服务重映射,列表形式,元素为元组 ('chatter', 'chat') → 把 /chatter 改成 /chat
parameters 给节点设置参数,字典形式 {'publish_freq': 2.0} → 等价于 --ros-args -p publish_freq:=2.0
output='screen' 节点日志输出到终端(调试必备) -
respawn=True 节点崩溃自动重启 -
Launch 文件的部署与运行
1. 部署 Launch 文件

Python 功能包(setup.py 配置)

setup.pydata_files 中添加 launch 目录部署:

复制代码
from glob import glob
import os

setup(
    # 其他配置不变
    data_files=[
        # 原有配置(如package.xml、action文件)...
        # 部署launch目录
        ('share/' + PACKAGE_NAME + '/launch', glob(os.path.join('launch', '*.launch.py'))),
    ],
)

C++ 功能包(CMakeLists.txt 配置)

复制代码
# 部署launch目录
install(DIRECTORY
  launch
  DESTINATION share/${PROJECT_NAME}/
)
2. 常用 Launch 运行命令
复制代码
# 1. 基础运行(使用默认参数)
ros2 launch demo_py_node demo_launch.py

# 2. 传递命令行参数(覆盖默认值)
ros2 launch demo_py_node demo_launch.py target_count:=15

# 3. 查看Launch文件的帮助信息(查看可传的参数)
ros2 launch demo_py_node demo_launch.py --show-args

# 4. 启动时指定日志级别
ros2 launch demo_py_node demo_launch.py --ros-args --log-level DEBUG

# 5. 仅打印Launch文件的执行计划(不实际启动节点,调试用)
ros2 launch demo_py_node demo_launch.py --dry-run
3. 运行效果

执行 ros2 launch demo_py_node demo_launch.py 后:

  • 终端会批量启动 publisher_nodesubscriber_nodecount_action_server_node
  • 所有节点的日志都会输出到当前终端(因 output='screen');
  • 话题会被重映射为 /demo/chat,避免和其他节点冲突;
  • publisher_node 崩溃,会在 3 秒后自动重启。
Launch 进阶技巧
1. 嵌套引入其他 Launch 文件

如果有多个 Launch 文件(如 sensor_launch.pynav_launch.py),可在主 Launch 文件中嵌套引入:

复制代码
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from ament_index_python.packages import get_package_share_directory
import os

# 引入其他Launch文件
sensor_launch = IncludeLaunchDescription(
    PythonLaunchDescriptionSource(
        os.path.join(
            get_package_share_directory('demo_py_node'),  # 功能包路径
            'launch/sensor_launch.py'  # 嵌套的Launch文件
        )
    ),
    # 给嵌套的Launch文件传参数
    launch_arguments={'sensor_type': 'lidar', 'freq': '10.0'}.items()
)

# 在LaunchDescription中添加
return LaunchDescription([
    sensor_launch,
    # 其他节点...
])
2. 条件启动节点

根据命令行参数决定是否启动某个节点(如仅在调试模式启动 RViz):

复制代码
from launch.conditions import IfCondition

# 声明调试模式参数
debug_arg = DeclareLaunchArgument(
    'debug',
    default_value='false',
    description='是否启动调试模式(启动RViz)'
)

# 条件启动RViz节点
rviz_node = Node(
    package='rviz2',
    executable='rviz2',
    name='rviz2',
    output='screen',
    condition=IfCondition(LaunchConfiguration('debug'))  # 仅debug=true时启动
)

# 组装
return LaunchDescription([
    debug_arg,
    rviz_node,
    # 其他节点...
])

# 运行时启动调试模式:
# ros2 launch demo_py_node demo_launch.py debug:=true
总结
核心知识点
  1. Launch 是 ROS2 批量启动节点的工具,推荐使用 Python 版(.launch.py),支持逻辑控制和动态配置;
  2. 核心组件是 Node(启动节点)、Remap(重映射)、Parameter(参数)、LaunchConfiguration(命令行参数);
  3. Launch 文件需部署到 share/包名/launch/ 目录,运行命令为 ros2 launch 包名 文件名.launch.py
高频操作

调试必备:output='screen'(日志输出到终端)、--dry-run(预览启动计划);

动态配置:用 DeclareLaunchArgument 声明命令行参数,LaunchConfiguration 引用;

模块化:用 IncludeLaunchDescription 嵌套引入其他 Launch 文件,拆分复杂启动逻辑。

7.tf

TF2 核心认知

TF2(Transform Library 2)是 ROS2 用于跟踪、广播、查询多坐标系之间变换关系的框架,核心解决 "不同传感器 / 部件的坐标统一" 问题:

  • 🧭 坐标系(Frame) :每个部件 / 传感器对应一个唯一坐标系(如 base_link(机器人基坐标)、laser(激光雷达坐标)、map(地图坐标));
  • 📡 TF 广播器(TF Broadcaster):发布两个坐标系之间的变换关系(如 "激光雷达相对于基坐标的位置");
  • 🔍 TF 监听器(TF Listener):查询任意两个坐标系之间的变换关系(如 "从激光雷达坐标转换到地图坐标");
  • 时间戳:TF2 会缓存变换关系(默认 10 秒),支持查询 "历史时刻" 的坐标变换(如 "1 秒前激光雷达相对于地图的位置")。
TF2 解决的核心问题

机器人上的传感器(激光、相机、IMU)都有自己的局部坐标系,TF2 能将这些局部坐标统一到全局坐标系(如地图),比如:

  • 激光雷达检测到障碍物在自身坐标系的 (1,0,0) 位置 → 通过 TF2 转换为地图坐标系的 (10,5,0)
  • 导航指令要求机器人移动到地图坐标系的 (20,8,0) → 通过 TF2 转换为机器人基坐标的运动指令。
TF2 关键知识点
1. 坐标系命名规范

为避免混乱,ROS2 对常用坐标系有统一命名,新手建议遵循:

坐标系名 含义 示例
map 全局地图坐标系(固定) 导航的全局参考系
odom 里程计坐标系(随机器人移动) 基于轮式里程计的局部参考系
base_link 机器人基坐标系(核心) 机器人底盘中心
base_footprint 机器人底盘接地坐标系 用于地面高度参考
laser/lidar 激光雷达坐标系 激光雷达安装位置
camera 相机坐标系 相机光心位置
2. TF2 变换的两种类型
变换类型 特点 适用场景
静态变换(Static TF) 变换关系永久不变 传感器相对于机器人基坐标的安装位置(如激光雷达固定在底盘)
动态变换(Dynamic TF) 变换关系实时变化 机器人基坐标相对于地图的位置(导航时实时更新)
3. TF2 的核心通信机制

TF2 底层基于 话题(Topic) 实现:

  • 静态变换:通过 /tf_static 话题发布(QoS 为 "永久保留",只发一次);
  • 动态变换:通过 /tf 话题发布(周期性更新);
  • 所有变换关系可通过 ros2 topic echo /tfros2 topic echo /tf_static 查看。
4. TF2 变换的表示方式

TF2 中的坐标变换包含平移(Translation)旋转(Rotation)

  • 平移:用 x/y/z 表示坐标系原点的偏移(如激光雷达在基坐标的 x=0.2, y=0, z=0.1);
  • 旋转:常用四元数(x/y/z/w)或欧拉角(滚转 / 俯仰 / 偏航)表示,ROS2 优先用四元数(避免万向锁)。
5. TF2 的核心概念:

变换树(TF Tree)

所有坐标系通过 "父子关系" 组成一棵变换树,必须无环、无孤立节点,比如:

复制代码
map → odom → base_link → laser
            ↳ base_link → camera
  • mapodom 的父节点,odombase_link 的父节点;
  • 只能查询 "变换树中存在路径" 的两个坐标系(如 maplaser 可行,mapimu 需先添加 base_link → imu 变换)。
TF2 常用工具 / 命令
1. 基础查询工具
(1)查看所有坐标系(tf2_tools)
复制代码
# 安装tf2工具(若未安装)
sudo apt install ros-humble-tf2-tools

# 查看当前所有坐标系及父子关系(可视化变换树)
ros2 run tf2_tools view_frames
# 生成frames.pdf文件,用PDF阅读器打开即可看到变换树
evince frames.pdf
(2)查询两个坐标系的变换关系(最常用)
复制代码
# 通用格式:ros2 run tf2_ros tf2_echo <父坐标系> <子坐标系>
# 示例1:查询激光雷达(laser)相对于基坐标(base_link)的变换(静态)
ros2 run tf2_ros tf2_echo base_link laser

# 示例2:查询基坐标(base_link)相对于地图(map)的变换(动态)
ros2 run tf2_ros tf2_echo map base_link

# 输出说明:
# - Translation:平移(x/y/z)
# - Rotation:旋转(四元数+欧拉角)
# 动态变换会实时刷新,静态变换只输出一次
2. 话题调试工具
复制代码
# 1. 查看TF话题列表
ros2 topic list | grep tf  # 输出 /tf /tf_static

# 2. 查看TF话题数据(静态变换)
ros2 topic echo /tf_static

# 3. 查看TF话题数据(动态变换)
ros2 topic echo /tf

# 4. 查看TF话题发布频率(动态变换)
ros2 topic hz /tf
3. 静态变换发布工具

新手无需写代码,可直接用命令发布静态 TF(适合调试传感器安装位置):

复制代码
# 通用格式:ros2 run tf2_ros static_transform_publisher x y z yaw pitch roll 父坐标系 子坐标系
# 参数说明:x/y/z(平移),yaw/pitch/roll(旋转,单位:弧度,绕z/y/x轴)
# 示例:发布laser相对于base_link的静态变换(x=0.2, y=0, z=0.1,无旋转)
ros2 run tf2_ros static_transform_publisher 0.2 0 0.1 0 0 0 base_link laser

# 可选:用四元数指定旋转(适合复杂旋转)
ros2 run tf2_ros static_transform_publisher x y z qx qy qz qw 父坐标系 子坐标系
4. RViz2 可视化 TF

RViz2 是 TF2 可视化的核心工具,能直观看到所有坐标系的位置关系:

复制代码
# 启动RViz2
rviz2

# 在RViz2中配置TF可视化:
# 1. 左侧Displays面板,点击Add → 选择TF → OK;
# 2. 勾选TF的Show Names(显示坐标系名)、Show Axes(显示坐标轴);
# 3. 设置Fixed Frame为map(全局参考系),即可看到所有坐标系的位置。
5. 动态变换调试工具
复制代码
# 查看TF缓存状态(调试历史变换查询)
ros2 run tf2_ros tf2_monitor base_link laser

# 检查TF变换树是否有错误(如环、孤立节点)
ros2 run tf2_tools check_tf_tree
TF2 代码开发核心模板
1. 静态 TF 发布
复制代码
#!/usr/bin/env python3
"""ROS2 静态TF发布模板"""
import rclpy
from rclpy.node import Node
from tf2_ros import StaticTransformBroadcaster
from geometry_msgs.msg import TransformStamped
import tf_transformations  # 用于四元数转换

class StaticTFBroadcaster(Node):
    def __init__(self):
        super().__init__("static_tf_broadcaster_node")
        # 创建静态TF广播器
        self.tf_broadcaster = StaticTransformBroadcaster(self)
        
        # 构造变换关系
        t = TransformStamped()
        t.header.stamp = self.get_clock().now().to_msg()
        t.header.frame_id = "base_link"  # 父坐标系
        t.child_frame_id = "laser"       # 子坐标系
        
        # 设置平移(x=0.2, y=0, z=0.1)
        t.transform.translation.x = 0.2
        t.transform.translation.y = 0.0
        t.transform.translation.z = 0.1
        
        # 设置旋转(无旋转,四元数为(0,0,0,1))
        q = tf_transformations.quaternion_from_euler(0, 0, 0)
        t.transform.rotation.x = q[0]
        t.transform.rotation.y = q[1]
        t.transform.rotation.z = q[2]
        t.transform.rotation.w = q[3]
        
        # 发布静态TF(只发一次)
        self.tf_broadcaster.sendTransform(t)
        self.get_logger().info("已发布base_link → laser的静态TF")

def main(args=None):
    rclpy.init(args=args)
    node = StaticTFBroadcaster()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == "__main__":
    main()
2. 动态 TF 发布
复制代码
#!/usr/bin/env python3
"""ROS2 动态TF发布模板(模拟base_link→odom的动态变换)"""
import rclpy
from rclpy.node import Node
from tf2_ros import TransformBroadcaster
from geometry_msgs.msg import TransformStamped
import tf_transformations

class DynamicTFBroadcaster(Node):
    def __init__(self):
        super().__init__("dynamic_tf_broadcaster_node")
        # 创建动态TF广播器
        self.tf_broadcaster = TransformBroadcaster(self)
        # 定时器(10Hz发布动态TF)
        self.timer = self.create_timer(0.1, self.timer_callback)
        self.x = 0.0  # 模拟x轴平移递增

    def timer_callback(self):
        t = TransformStamped()
        t.header.stamp = self.get_clock().now().to_msg()
        t.header.frame_id = "odom"       # 父坐标系
        t.child_frame_id = "base_link"   # 子坐标系
        
        # 动态更新平移(x轴每秒移动0.1m)
        t.transform.translation.x = self.x
        t.transform.translation.y = 0.0
        t.transform.translation.z = 0.0
        
        # 旋转(无旋转)
        q = tf_transformations.quaternion_from_euler(0, 0, 0)
        t.transform.rotation.x = q[0]
        t.transform.rotation.y = q[1]
        t.transform.rotation.z = q[2]
        t.transform.rotation.w = q[3]
        
        # 发布动态TF
        self.tf_broadcaster.sendTransform(t)
        self.x += 0.01  # 每次回调x增加0.01
        self.get_logger().info(f"发布动态TF:odom → base_link,x={self.x:.2f}")

def main(args=None):
    rclpy.init(args=args)
    node = DynamicTFBroadcaster()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == "__main__":
    main()
3. TF 监听
复制代码
#!/usr/bin/env python3
"""ROS2 TF监听模板(查询laser→map的变换)"""
import rclpy
from rclpy.node import Node
from tf2_ros import Buffer, TransformListener
import tf2_ros

class TFListener(Node):
    def __init__(self):
        super().__init__("tf_listener_node")
        # 创建TF缓存和监听器
        self.tf_buffer = Buffer()
        self.tf_listener = TransformListener(self.tf_buffer, self)
        # 定时器(1Hz查询变换)
        self.timer = self.create_timer(1.0, self.timer_callback)

    def timer_callback(self):
        try:
            # 查询laser相对于map的变换(最新时刻)
            # 参数:子坐标系、父坐标系、查询时间(rclpy.time.Time()表示最新)
            trans = self.tf_buffer.lookup_transform(
                "map",
                "laser",
                rclpy.time.Time()
            )
            self.get_logger().info(f"laser → map 平移:x={trans.transform.translation.x:.2f}")
        except tf2_ros.TransformException as e:
            self.get_logger().warn(f"无法查询变换:{e}")

def main(args=None):
    rclpy.init(args=args)
    node = TFListener()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == "__main__":
    main()
常见问题排查

tf2_echo 查询失败 "Could not find a connection between [map] and [laser]"

  • 检查变换树是否完整(如 map → odom → base_link → laser 路径是否存在);
  • 检查 TF 广播器是否运行,坐标系名是否拼写正确(区分大小写);
  • view_frames 查看变换树,确认无孤立节点。

静态 TF 发布后无效果

  • 静态 TF 发布命令的参数顺序是否正确(x y z yaw pitch roll 父 子);
  • 检查 /tf_static 话题是否有数据(ros2 topic echo /tf_static)。

RViz2 中看不到坐标系

  • 确认 Fixed Frame 设置正确(如 map);
  • 勾选 TF 面板的 Show Axes 和 Show Names;
  • 检查 TF 广播器是否正常发布变换。
总结
核心知识点
  1. TF2 是 ROS2 处理坐标系变换的核心框架,分为静态 TF (固定变换)和动态 TF(实时变换);
  2. 所有坐标系组成变换树,必须无环、无孤立节点,才能查询任意两坐标系的变换;
  3. TF2 底层基于 /tf(动态)和 /tf_static(静态)话题发布变换关系。
高频工具 / 命令
  1. 查变换树:ros2 run tf2_tools view_frames + evince frames.pdf
  2. 查变换关系:ros2 run tf2_ros tf2_echo 父坐标系 子坐标系
  3. 发布静态 TF:ros2 run tf2_ros static_transform_publisher x y z yaw pitch roll 父 子
  4. 可视化:RViz2 中添加 TF 显示,设置 Fixed Frame 为全局坐标系。
开发核心
  1. 静态 TF:用 StaticTransformBroadcaster 发布(只发一次);
  2. 动态 TF:用 TransformBroadcaster + 定时器周期性发布;
  3. 查变换:用 Buffer + TransformListenerlookup_transform 方法。

8.rqt

RQT 核心认知

RQT(ROS Qt GUI)是 ROS2 基于 Qt 开发的模块化图形化工具框架,核心特点:

  • 🎨 可视化调试:用图形界面替代终端命令,直观查看话题数据、节点关系、TF 变换等;
  • 🔩 插件化设计 :RQT 本身是框架,功能通过 "插件" 实现(如 rqt_graph 查看节点图、rqt_plot 绘制数据曲线);
  • 🚀 跨平台:支持 Linux/Windows/macOS,和 ROS2 核心功能深度集成;
  • 📌 一键启动 :通过 rqt 命令启动主界面,或直接启动单个插件(如 rqt_graph)。
RQT 优势
终端调试 RQT 可视化调试
需记忆大量命令(如 ros2 topic echo/ros2 param set 点选操作,无需记命令
纯文本输出,数据无直观性(如高频数值) 曲线 / 图表 / 树形结构展示,数据趋势一目了然
单次操作(如改参数需重新输入命令) 实时交互(如拖动滑块改参数,即时生效)
RQT 知识点
1. RQT 的安装

ROS2 桌面版(ros-humble-desktop)已默认安装 RQT,若未安装可手动补全:

复制代码
# 安装RQT核心包
sudo apt install ros-humble-rqt
# 安装常用插件(推荐)
sudo apt install ros-humble-rqt-common-plugins ros-humble-rqt-tf-tree
2. RQT 的启动方式
启动方式 命令 适用场景
启动主界面(所有插件) rqt 需同时使用多个插件(如看节点图 + 改参数)
启动单个插件 rqt_xxx(如 rqt_graph/rqt_plot 仅需单个功能,启动更快
启动指定插件(主界面) rqt --plugin rqt_graph 主界面默认打开指定插件
3. RQT 插件的通用操作
  • 插件切换 :主界面顶部 Plugins 菜单 → 选择对应插件(如 Visualization → Graph);
  • 重置布局Window → Reset Perspective(界面乱了时恢复默认);
  • 保存布局Window → Save Perspective As(自定义布局,下次直接加载);
  • 关闭插件 :插件窗口右上角 ×,或 Plugins → 已勾选插件 取消勾选。
4. RQT 与 ROS2 核心功能的映射

RQT 插件本质是封装了 ROS2 的终端命令,核心映射关系:

ROS2 终端命令 RQT 对应插件 功能
ros2 node list/ros2 topic list rqt_graph 可视化节点 / 话题 / 服务的关系图
ros2 topic echo/ros2 topic hz rqt_plot/rqt_topic 查看 / 绘制话题数据
ros2 param set/ros2 param get rqt_reconfigure(或 rqt_param 实时修改 / 查看节点参数
ros2 service call rqt_service_caller 可视化调用服务
ros2 action send_goal rqt_action 可视化发送动作目标
ros2 run tf2_tools view_frames rqt_tf_tree 可视化 TF 变换树
RQT 常用工具(插件)详解
1. rqt_graph
作用

直观展示当前运行的节点、话题、服务、动作之间的连接关系,是调试 "节点是否通信" 的首选工具。

复制代码
# 直接启动
rqt_graph
# 或从RQT主界面启动:Plugins → Visualization → Graph
关键操作
  • 刷新:点击左上角刷新按钮(或按 F5),更新节点图;

  • 过滤

    • Nodes only:只显示节点(隐藏话题 / 服务);
    • Hide debug:隐藏调试类话题(如 /rosout);
    • 搜索框输入关键词(如 chatter),过滤相关节点 / 话题;
  • 缩放 / 平移:鼠标滚轮缩放,左键拖动平移;

  • 节点颜色:不同颜色代表不同节点,连线代表话题 / 服务通信。

实操示例

运行之前的话题发布 / 订阅节点,启动 rqt_graph

  • 能看到 publisher_nodesubscriber_node 之间有连线(对应 /chatter 话题);
  • 连线标注 std_msgs/msg/String,表示通信的消息类型;
  • 验证节点是否正确建立通信(无连线则说明话题名 / 类型不匹配)。
2. rqt_plot(数值话题曲线绘制)
作用

实时绘制数值型话题 的曲线(如 std_msgs/msg/Int32geometry_msgs/msg/Twist 的 x 轴速度),直观查看数据趋势。

启动与使用
复制代码
# 直接启动
rqt_plot
# 或从RQT主界面启动:Plugins → Visualization → Plot
关键操作
  • 添加话题

    :在顶部输入框填写话题路径(如

    复制代码
    /chatter/data

    复制代码
    /cmd_vel/linear/x

    ),按回车添加;

    • 支持嵌套字段:如 /twist/linear/x(Twist 消息的线速度 x 轴);
  • 调整坐标轴

    • 鼠标滚轮缩放 Y 轴,右键拖动平移;
    • 点击 Auto-scale 自动适配数据范围;
  • 数据保存File → Save Data,将曲线数据保存为 CSV 文件;

  • 清除曲线 :选中曲线 → 右键 → Remove

实操示例

运行发布 Twist 消息的节点,在 rqt_plot 中输入 /cmd_vel/linear/x

  • 能看到曲线实时跟随线速度值变化;
  • 验证速度指令是否稳定(曲线抖动 / 突变则说明指令异常)。
3. rqt_topic(话题综合调试)
作用

一站式查看所有话题的发布频率、消息类型、实时数据 ,替代 ros2 topic list/echo/hz 等命令。

启动与使用
复制代码
# 直接启动
rqt_topic
# 或从RQT主界面启动:Plugins → Topics → Topic Monitor
关键操作
  • 查看话题列表:左侧列表显示所有活跃话题,包含消息类型、发布者 / 订阅者数量;
  • 实时查看数据 :选中话题 → 右侧 Message 标签页,实时显示消息内容;
  • 查看发布频率Hz 标签页,显示话题的发布频率(等价于 ros2 topic hz);
  • 手动发布话题Publish 标签页,填写消息数据 → 点击 Publish,替代 ros2 topic pub 命令。
4. rqt_reconfigure(参数动态调整)
作用

可视化调整节点的动态参数 (需节点使用 rclpy.parameterdynamic_reconfigure),实时生效无需重启节点。

启动与使用
复制代码
# 直接启动
rqt_reconfigure
# 或从RQT主界面启动:Plugins → Configuration → Dynamic Reconfigure
关键操作
  • 选择节点 :左侧列表选择要调整的节点(如 publisher_node);

  • 调整参数

    • 数值型参数:拖动滑块 / 直接输入值,自动生效;
    • 布尔型参数:勾选 / 取消勾选;
    • 枚举型参数:下拉选择;
  • 保存参数File → Save Config,将参数配置保存为文件,下次加载使用。

注意

仅支持标记为 "动态可修改" 的参数,若节点未配置动态参数,此插件中看不到对应节点。

5. rqt_service_caller(服务可视化调用)
作用

可视化调用服务,替代 ros2 service call 命令,无需手动编写请求数据格式。

启动与使用
复制代码
# 直接启动
rqt_service_caller
# 或从RQT主界面启动:Plugins → Services → Service Caller
关键操作
  • 选择服务 :下拉框选择要调用的服务(如 /reset_count);
  • 填写请求数据 :自动生成请求表单(如 SetBool 服务的 data 复选框);
  • 调用服务 :点击 Call,右侧显示响应结果(成功 / 失败 + 消息);
  • 重复调用 :勾选 Repeat,设置调用频率(如 1Hz),自动重复调用。
6. rqt_tf_tree(TF 变换树可视化)
作用

直观展示 TF 变换树的父子关系、坐标系位置 ,替代 ros2 run tf2_tools view_frames 命令,支持实时刷新。

启动与使用
复制代码
# 直接启动
rqt_tf_tree
# 或从RQT主界面启动:Plugins → Visualization → TF Tree
关键操作
  • 刷新变换树 :点击 Refresh,实时更新坐标系关系;
  • 查看变换详情:鼠标悬停在连线上,显示平移 / 旋转参数;
  • 导出图片File → Save as,将变换树保存为 PNG/PDF 文件;
  • 设置参考系 :下拉框选择 Fixed Frame,调整可视化的参考坐标系。
7. rqt_console(日志可视化查看)
作用

集中查看所有节点的日志信息(debug/info/warn/error/fatal),支持过滤、搜索、保存日志。

启动与使用
复制代码
# 直接启动
rqt_console
# 或从RQT主界面启动:Plugins → Logging → Console
关键操作
  • 过滤日志级别 :勾选顶部 Debug/Info/Warn/Error,只显示对应级别的日志;
  • 搜索日志 :输入关键词(如 count),过滤包含该关键词的日志;
  • 保存日志File → Save Log,将日志保存为文件,用于离线分析;
  • 清除日志 :点击 Clear,清空当前日志列表。
RQT 进阶技巧
1. 自定义 RQT 布局

若经常同时使用多个插件(如 rqt_graph + rqt_plot + rqt_console),可自定义布局并保存:

复制代码
# 1. 启动RQT主界面,打开需要的插件,调整窗口位置;
# 2. 保存布局:Window → Save Perspective As → 命名(如 `debug_layout`);
# 3. 下次启动:rqt --perspective debug_layout,直接加载自定义布局。
2. 远程使用 RQT(跨机器调试)

若 ROS2 节点运行在远程机器人上,本地启动 RQT 需配置 ROS2 环境变量:

复制代码
# 本地终端配置(替换为机器人IP)
export ROS_DOMAIN_ID=相同的域ID
export ROS2_CONTROLLER_URI=http://机器人IP:8080
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp  # 或其他DDS实现
# 启动RQT,即可连接远程节点
rqt
3. RQT 插件加载失败排查
  • 检查插件是否安装:apt list | grep ros-humble-rqt-xxx
  • 重启 RQT 并查看终端报错:rqt --debug,根据报错安装缺失依赖;
  • 重置 RQT 配置:rm -rf ~/.config/rqt/,重新启动。
常见问题排查
  1. rqt_graph 看不到节点

    • 检查节点是否运行(ros2 node list);
    • 确保 ROS_DOMAIN_ID 一致(不同域 ID 的节点无法互通);
    • 点击 Refresh 刷新节点图。
  2. rqt_plot 无法添加话题

    • 话题必须是数值型(如 String 类型无法绘制曲线);
    • 话题路径填写正确(如 /chatter 是 String 类型,需填 /chatter/data);
    • 检查话题是否有数据发布(ros2 topic echo 验证)。
  3. rqt_reconfigure 看不到节点

    • 节点未实现动态参数接口(需用 declare_parameter 并设置 dynamic_typing=True);
    • 节点未运行或名称拼写错误。
总结
核心知识点
  1. RQT 是 ROS2 图形化调试框架,功能通过插件实现,替代终端命令提升调试效率;
  2. 核心插件与 ROS2 功能一一对应:rqt_graph(节点关系)、rqt_plot(数值曲线)、rqt_topic(话题调试)、rqt_reconfigure(参数调整);
  3. RQT 支持自定义布局、远程调试,是 ROS2 开发调试的必备工具。
高频工具优先级
  1. rqt_graph:优先掌握,验证节点通信是否正常;
  2. rqt_topic:一站式查看话题数据 / 频率,替代多个终端命令;
  3. rqt_plot:可视化数值数据,快速定位数据异常;
  4. rqt_console:集中查看日志,排查节点报错。
关键操作
  1. 启动方式:直接 rqt_xxx 启动单个插件,或 rqt 启动主界面;
  2. 核心逻辑:RQT 插件是终端命令的可视化封装,操作逻辑和终端命令一致;
  3. 调试流程:先用 rqt_graph 确认通信,再用 rqt_topic/rqt_plot 查看数据,最后用 rqt_reconfigure 调整参数。

9.rviz

RViz2 核心认知(先懂本质)

RViz2(ROS Visualization 2)是 ROS2 官方推出的三维可视化工具 ,核心定位是「机器人开发的可视化调试面板」,不参与机器人控制 / 计算,只负责「数据可视化展示」:

  • 🎯 核心作用:将 ROS2 中的抽象数据(如激光点云、坐标变换、路径、机器人模型)转化为直观的 3D/2D 图形;
  • 🔌 适配性:支持几乎所有 ROS2 标准数据类型(激光、图像、IMU、TF、路径等),兼容自定义数据(需配置插件);
  • 🚀 实时性:与 ROS2 话题 / 服务深度联动,数据实时刷新,调试时能即时看到效果;
  • 📌 灵活性:支持自定义视角、保存配置、加载插件,适配不同机器人场景(无人机、机械臂、移动机器人)。
RViz2 解决的核心问题

机器人开发中很多数据是「看不见摸不着」的,RViz2 能帮你快速验证逻辑是否正确:

  • 激光雷达是否正常工作?→ 在 RViz2 看激光点云是否完整;
  • TF 坐标是否配置正确?→ 在 RViz2 看各坐标系位置是否符合预期;
  • 导航路径是否合理?→ 在 RViz2 看规划的路径是否避开障碍物;
  • 无人机姿态是否正确?→ 在 RViz2 看无人机模型的俯仰 / 滚转 / 偏航是否匹配实际。
RViz2 关键知识点
1. 安装与启动(ROS2 环境)

ROS2 桌面版(ros-humble-desktop/ros-galactic-desktop)已默认安装,若未安装手动补全:

复制代码
# 安装RViz2核心包
sudo apt install ros-humble-rviz2
# 安装常用依赖(确保功能完整)
sudo apt install ros-humble-rviz-common ros-humble-rviz-default-plugins
启动方式(3 种常用)
复制代码
# 1. 基础启动(默认配置)
rviz2

# 2. 启动并加载自定义配置文件(最常用,无需重复配置)
rviz2 -d 你的配置文件路径.rviz

# 3. 从launch文件启动(推荐,和机器人节点一起启动)
# 在launch文件中添加Node配置,后续会讲
2. RViz2 核心界面组成
  1. Displays 面板(左侧,核心!):配置要显示的「数据类型」(如 TF、激光、机器人模型),所有可视化内容都需在这里添加 / 启用;
  2. 3D 可视化视图(中间,主区域):显示机器人、传感器数据、坐标系等核心内容,是调试的核心观察区;
  3. Views 面板(右侧上方):调整 3D 视图的视角(如第三人称、俯视图、跟随机器人),支持保存常用视角;
  4. Time 面板(右侧中间):控制时间同步(如实时显示 / 回放历史数据),新手默认用「Real Time」即可;
  5. 工具栏(顶部):快捷操作(视角调整、保存配置、重置视图),核心按钮要记牢。
3. 核心概念:Fixed Frame
  • 定义:RViz2 所有可视化数据的「全局参考坐标系」,所有数据都会基于这个坐标系显示位置;
  • 作用:若 Fixed Frame 配置错误,会导致「数据不显示」或「显示位置混乱」;
  • 新手推荐配置:优先选 map(全局地图坐标系)或 base_link(机器人基坐标系),根据场景切换;
  • 配置位置:Displays 面板顶部的「Fixed Frame」下拉框选择。
4. 数据展示逻辑

RViz2 本质是「话题订阅者」,所有可视化数据都来自 ROS2 话题,核心逻辑:

  1. 机器人节点发布数据到指定话题(如激光数据→/scan、TF→/tf);

  2. RViz2 在 Displays 面板添加对应「显示类型」,并订阅目标话题;

  3. RViz2 接收话题数据,转化为 3D 图形展示在主视图。

    → 核心结论:

    要显示某类数据,必须满足「机器人发布对应话题 + RViz2 配置对应显示类型」

RViz2 常用功能实操

所有功能都围绕「Displays 面板添加显示项」展开,以下是新手最常用的 8 大功能,附详细配置步骤。

前提:基础准备

启动 RViz2 后,先做 2 个基础配置(避免后续踩坑):

  1. Displays 面板顶部「Fixed Frame」:选择 base_link(先以机器人基坐标为参考);
  2. 工具栏点击「重置视角」(Reset View),让视图回到默认位置。
功能 1:显示 TF 坐标变换(必备!验证坐标系配置)

核心用途:查看所有坐标系的位置和父子关系,验证 TF 广播是否正确(如激光、相机相对于机器人的位置)

配置步骤

  1. Displays 面板点击「Add」→ 左侧列表选择「TF」→ 点击「OK」;

  2. 启用 TF 显示项(默认勾选),主视图会显示各坐标系的「坐标轴」(X 红、Y 绿、Z 蓝);

  3. 关键配置(优化显示):

    • 勾选「Show Names」:显示坐标系名称(如 base_linklaser);
    • 勾选「Show Axes」:显示坐标轴(默认勾选);
    • 调整「Axis Length」:坐标轴长度(默认 1m,按需调大 / 小);
  4. 验证:若 TF 广播正常,能看到各坐标系按实际安装位置排列(如 laserbase_link 前方 0.2m 处)。

功能 2:显示机器人模型(URDF)

核心用途:可视化机器人的 3D 模型,验证 URDF 文件是否正确(无人机、机械臂、移动机器人都适用)

配置步骤

  1. 前提:机器人节点已发布 URDF 到 /robot_description 话题(大部分机器人启动后会自动发布);

  2. Displays 面板「Add」→ 选择「RobotModel」→「OK」;

  3. 关键配置(必改):

    • 「Robot Description Topic」:默认 /robot_description(无需改,和节点发布一致即可);
    • 「Visual Enabled」:勾选(显示机器人外观);
    • 「Collision Enabled」:可选勾选(显示碰撞箱,调试避障用);
  4. 效果:主视图会显示机器人的 3D 模型,模型姿态会跟随 TF 实时变化。

功能 3:显示激光雷达数据(核心!激光调试必备)

核心用途:查看激光雷达的点云数据,验证激光是否正常、是否有障碍物

配置步骤(适配标准激光数据 sensor_msgs/msg/LaserScan

  1. Displays 面板「Add」→ 选择「LaserScan」→「OK」;

  2. 关键配置(必改!否则不显示):

    • 「Topic」:选择激光话题(通常是 /scan,按实际节点发布的话题选择);
    • 「Fixed Frame」:若全局 Fixed Frame 不对,可单独设置为 laser(激光坐标系);
  3. 优化显示:

    • 「Size (m)」:调整激光点的大小(默认 0.05m,调大更清晰);
    • 「Color」:选择激光点颜色(默认白色,可改红色);
  4. 验证:激光正常工作时,主视图会显示一圈点云,对应周围环境(有障碍物的地方有点,空旷处无点)。

功能 4:显示图像数据(相机调试必备)

核心用途:查看相机采集的图像,验证相机是否正常工作(支持单目 / 双目相机)

配置步骤(适配标准图像数据 sensor_msgs/msg/Image

  1. Displays 面板「Add」→ 选择「Image」→「OK」;

  2. 关键配置:

    • 「Topic」:选择相机话题(通常是 /camera/image_raw,按实际选择);
    • 「Image Transport」:默认「raw」(无需改);
  3. 效果:RViz2 会弹出独立窗口显示相机实时图像,也可在主视图侧边显示(调整「Position」参数)。

功能 5:显示路径(导航 / 无人机航迹必备)

核心用途:查看导航规划的路径或无人机的飞行航迹,验证路径规划是否合理

配置步骤(适配标准路径数据 nav_msgs/msg/Path

  1. Displays 面板「Add」→ 选择「Path」→「OK」;

  2. 关键配置:

    • 「Topic」:选择路径话题(如导航路径 /plan、无人机航迹 /flight_path);
    • 「Color」:设置路径颜色(推荐红色,醒目);
    • 「Line Width」:调整路径线条粗细(默认 0.02m);
  3. 效果:主视图会显示规划的路径线条,路径节点会实时更新。

功能 6:显示姿态(无人机 / 机器人姿态必备)

核心用途:查看机器人 / 无人机的姿态(俯仰、滚转、偏航),验证 IMU 数据或姿态解算是否正确

配置步骤(适配标准姿态数据 geometry_msgs/msg/PoseStamped

  1. Displays 面板「Add」→ 选择「Pose」→「OK」;

  2. 关键配置:

    • 「Topic」:选择姿态话题(如 /drone/pose);
    • 「Shape」:选择姿态显示样式(箭头 / 轴 / 球体,推荐「Arrow」);
    • 「Arrow Length」:调整箭头长度(默认 1m,适配无人机大小);
  3. 效果:箭头方向对应机器人 / 无人机的朝向,箭头姿态实时跟随话题数据变化。

功能 7:显示点云(3D 激光 / 深度相机必备)

核心用途:查看 3D 激光或深度相机的点云数据,构建环境三维模型

配置步骤(适配标准点云数据 sensor_msgs/msg/PointCloud2

  1. Displays 面板「Add」→ 选择「PointCloud2」→「OK」;

  2. 关键配置:

    • 「Topic」:选择点云话题(如 /points_raw);
    • 「Fixed Frame」:设置为 lidar(3D 激光坐标系);
    • 「Color Transformer」:选择点云着色方式(「RGB8」按颜色着色,「Intensity」按强度着色);
  3. 优化:调整「Size」让点云更清晰,勾选「Autocompute Intensity Bounds」自动适配强度范围。

功能 8:保存 / 加载配置

每次调试都要重新添加显示项?保存配置文件,下次直接加载,效率翻倍!

保存配置
  1. 配置完所有显示项后,点击顶部菜单栏「File」→「Save Config As」;
  2. 选择保存路径(建议存在功能包的 config/ 目录下),命名为 xxx.rviz(如 drone_debug.rviz);
加载配置
复制代码
# 方式1:命令行加载
rviz2 -d ~/ros2_ws/src/你的包名/config/drone_debug.rviz

# 方式2:launch文件加载(推荐,和节点一起启动)
# 后续launch示例会包含
实战配置示例:无人机调试 RViz2 配置

以「无人机 + 激光雷达」为例,提供完整的 RViz2 配置逻辑,可直接复用:

1. 核心显示项配置清单
  1. Fixed Frame:base_link(无人机基坐标系);
  2. TF:显示无人机各坐标系(base_linklaserimu);
  3. RobotModel:显示无人机 3D 模型(话题 /robot_description);
  4. LaserScan:显示激光数据(话题 /scan);
  5. Pose:显示无人机姿态(话题 /drone/pose);
  6. Path:显示飞行航迹(话题 /drone/flight_path)。
2. Launch 文件集成 RViz2

在 Launch 文件中添加 RViz2 节点,启动时自动加载配置,无需手动操作:

复制代码
# 无人机调试launch文件(drone_debug.launch.py)
from launch import LaunchDescription
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
import os

def generate_launch_description():
    # 1. 获取配置文件路径
    config_dir = os.path.join(get_package_share_directory('你的包名'), 'config')
    rviz_config = os.path.join(config_dir, 'drone_debug.rviz')

    # 2. 启动RViz2节点,加载配置
    rviz2_node = Node(
        package='rviz2',
        executable='rviz2',
        name='rviz2',
        output='screen',
        arguments=['-d', rviz_config]  # 加载自定义配置
    )

    # 其他节点(无人机节点、激光节点...)
    drone_node = Node(...)
    laser_node = Node(...)

    return LaunchDescription([
        drone_node,
        laser_node,
        rviz2_node  # 最后启动RViz2
    ])
常见问题排查
  1. 数据不显示?→ 先查 3 点:

    • Fixed Frame 是否配置正确(优先试 base_link);
    • 对应话题是否有数据(ros2 topic echo 话题名 验证);
    • Displays 面板中「Topic」是否选对,显示项是否勾选「Enabled」;
  2. 激光点云乱飘?→ 激光坐标系(laser)的 TF 配置错误,重新校准安装位置;

  3. 机器人模型不显示?→ 确认 /robot_description 话题有数据,RobotModel 的「Topic」配置正确;

  4. 视图看不清?→ 用工具栏调整视角(滚轮缩放、左键拖动平移、右键旋转),或重置视角。

总结
核心知识点
  1. RViz2 是「可视化调试工具」,核心是订阅 ROS2 话题并展示数据,不参与控制;
  2. Fixed Frame 是全局参考系,配置错误会导致数据不显示,新手优先用 base_link
  3. 所有可视化功能都需在「Displays 面板」添加对应显示项,并配置正确的话题。
新手必掌握的核心功能优先级
  1. TF 显示(验证坐标配置);
  2. 激光 / LaserScan 显示(传感器调试);
  3. 机器人模型 / RobotModel 显示(模型验证);
  4. 保存 / 加载配置(提升效率)。
关键操作
  1. 启动:rviz2rviz2 -d 配置文件.rviz
  2. 核心:Displays 面板「Add」添加显示项,配置「Topic」和「Fixed Frame」;
  3. 排错:先验证话题是否有数据,再检查 RViz2 配置。

10.ros2 bag

ROS2 Bag 核心认知

ROS2 Bag(简称 Bag)是 ROS2 专为话题数据设计的 "录音机 / 播放器":

  • 🎙️ 录制(record) :将指定话题的实时数据保存到二进制 Bag 文件中(后缀 .db3),包含数据内容、时间戳、消息类型等;
  • ▶️ 回放(play):从 Bag 文件中读取数据,按原时间戳(或自定义速度)重新发布到话题上,复现录制时的场景;
  • 📊 分析(info/filter):查看 Bag 文件的元信息(话题列表、数据量),或过滤提取指定数据;
  • 💡 核心价值:无需重复运行真实机器人 / 传感器,仅通过 Bag 回放就能复现问题、调试算法,大幅降低开发成本。
ROS2 Bag vs ROS1 Bag
特性 ROS2 Bag ROS1 Bag
文件格式 SQLite 数据库(.db3) 自定义格式(.bag)
录制范围 仅支持话题(Topic) 支持话题 / 服务 / 参数
DDS 兼容性 兼容所有 DDS 实现(CycloneDDS/FastDDS) 依赖 ROS Master
压缩方式 内置压缩(zstd/bz2) 需手动压缩
核心命令 ros2 bag record/play/info rosbag record/play/info
ROS2 Bag 关键知识点
1. Bag 文件的结构

ROS2 Bag 文件是一个 SQLite 数据库(.db3),包含以下核心信息:

  • 元数据:录制时间、ROS2 版本、话题列表、消息类型、数据总条数;
  • 话题数据:每条消息的内容、发布时间戳、话题名;
  • 索引:快速定位指定话题 / 时间范围的数据(回放 / 过滤时提速)。
2. 录制的核心参数
参数 作用 示例
-o/--output 指定 Bag 文件前缀(避免默认随机名) ros2 bag record -o lidar_data /scan
-a/--all 录制所有活跃话题(慎用,易产生大文件) ros2 bag record -a
-e/--regex 按正则表达式匹配话题 `ros2 bag record -e "/(scan cmd_vel)"`(录制 /scan 和 /cmd_vel)
--compression 启用压缩(降低文件大小) ros2 bag record -o data --compression zstd /chatter
--duration 指定录制时长(单位:秒) ros2 bag record --duration 60 /scan(录制 60 秒)
3. 回放的核心参数
参数 作用 示例
--rate 调整回放速度(倍数) ros2 bag play --rate 2.0 data_0.db3(2 倍速回放)
--topics 仅回放指定话题 ros2 bag play --topics /scan data_0.db3(只回放 /scan)
--start-offset 从指定时间偏移开始回放(秒) ros2 bag play --start-offset 10 data_0.db3(跳过前 10 秒)
--loop 循环回放 ros2 bag play --loop data_0.db3
--delay 话题发布前延迟(秒) ros2 bag play --delay 2 data_0.db3(延迟 2 秒开始)
4. 关键注意事项
  • 仅录制话题 :ROS2 Bag 只支持录制 / 回放话题(Topic),不支持服务(Service)、动作(Action)或参数(Parameter);
  • 时间戳对齐:回放时默认按录制的时间戳发布数据,确保和真实场景的时间节奏一致;
  • 文件大小 :高频话题(如相机 /image、激光 /scan)会快速生成大文件,建议按需录制(避免 -a);
  • 兼容性:不同 ROS2 版本录制的 Bag 文件可能不兼容(如 Humble 录制的文件无法在 Foxy 回放)。
ROS2 Bag 常用命令
1. 基础录制命令
复制代码
# 1. 录制单个话题(最常用)
ros2 bag record /chatter  # 录制/chatter,生成随机名文件(如data_2026_01_25_10_00_00)

# 2. 录制多个话题
ros2 bag record /chatter /cmd_vel /scan

# 3. 指定输出文件名(推荐,便于管理)
ros2 bag record -o my_lidar_data /scan  # 生成my_lidar_data_2026_01_25_10_00_00.db3

# 4. 录制所有话题(慎用,文件会很大)
ros2 bag record -a

# 5. 按正则表达式录制话题(灵活匹配)
ros2 bag record -e "/demo/.*"  # 录制所有以/demo/开头的话题
ros2 bag record -e "/(chatter|cmd_vel)"  # 录制/chatter和/cmd_vel

# 6. 录制并启用压缩(降低文件大小)
ros2 bag record -o compressed_data --compression zstd /scan  # zstd压缩(推荐,压缩率/速度均衡)
ros2 bag record -o compressed_data --compression bz2 /scan   # bz2压缩(压缩率更高,速度慢)

# 7. 录制指定时长(比如录制1分钟)
ros2 bag record --duration 60 /scan  # 60秒后自动停止录制
2. 查看 Bag 文件信息

录制后先查看 Bag 文件的元信息,确认数据是否正确:

复制代码
# 通用格式:ros2 bag info <Bag文件/目录>
ros2 bag info my_lidar_data_2026_01_25_10_00_00/  # Bag会生成目录,内含.db3文件

# 输出示例(关键信息标注):
# Files:             my_lidar_data_2026_01_25_10_00_00_0.db3
# Bag size:          12.3 MB
# Duration:          10.0s (录制时长)
# Start:             Jan 25 2026 10:00:00 (1740429600.000)
# End:               Jan 25 2026 10:00:10 (1740429610.000)
# Messages:          100 (总消息数)
# Topic information: Topic: /scan | Type: sensor_msgs/msg/LaserScan | Count: 100 | Serialization Format: CDR
3. 回放 Bag 文件
复制代码
# 基础回放(按原速度、发布所有话题)
ros2 bag play my_lidar_data_2026_01_25_10_00_00/

# 2倍速回放
ros2 bag play --rate 2.0 my_lidar_data_2026_01_25_10_00_00/

# 仅回放/scan话题
ros2 bag play --topics /scan my_lidar_data_2026_01_25_10_00_00/

# 跳过前5秒,从第5秒开始回放
ros2 bag play --start-offset 5 my_lidar_data_2026_01_25_10_00_00/

# 循环回放(直到手动停止)
ros2 bag play --loop my_lidar_data_2026_01_25_10_00_00/

# 回放时重映射话题名(将/scan改为/demo/scan)
ros2 bag play my_lidar_data_2026_01_25_10_00_00/ --remap /scan:=/demo/scan
4. 过滤 / 提取 Bag 数据

当 Bag 文件过大时,可过滤提取指定话题 / 时间范围的数据,生成新 Bag 文件:

复制代码
# 通用格式:ros2 bag filter <输入Bag> <输出Bag> <过滤表达式>

# 示例1:提取/scan话题,过滤掉其他话题
ros2 bag filter my_lidar_data_2026_01_25_10_00_00/ scan_only_bag "topic == '/scan'"

# 示例2:提取前5秒的所有数据
ros2 bag filter my_lidar_data_2026_01_25_10_00_00/ first_5s_bag "t.sec <= 1740429605"  # t是时间戳对象

# 示例3:提取/chat数据中内容包含"ROS2"的消息
ros2 bag filter chatter_bag/ ros2_only_bag "topic == '/chatter' and msg.data.find('ROS2') != -1"
5. 转换 Bag 文件格式(兼容 / 压缩)
复制代码
# 将 Bag 文件转换为压缩格式(降低大小)
ros2 bag convert -i my_data/ -o compressed_data/ --compression zstd

# 转换为 ROS1 Bag 格式(需安装 ros-humble-ros1-bridge)
ros2 bag convert -i my_data/ -o ros1_bag/ --output-format rosbag_v2
实操示例
步骤 1:录制话题数据
复制代码
# 1. 终端1:运行之前的话题发布节点(发布/chatter)
ros2 run demo_py_node topic_publisher

# 2. 终端2:录制/chatter话题,指定输出文件名chatter_data
ros2 bag record -o chatter_data /chatter

# 3. 录制10秒后,按 Ctrl+C 停止录制(终端会显示"Recording stopped")
步骤 2:查看 Bag 信息
复制代码
ros2 bag info chatter_data_2026_01_25_10_05_00/
# 确认输出包含:Topic: /chatter | Type: std_msgs/msg/String | Count: 10(10秒,1Hz发布)
步骤 3:回放 Bag 数据
复制代码
# 1. 终端1:运行话题订阅节点(接收/chatter)
ros2 run demo_py_node topic_subscriber

# 2. 终端2:回放Bag文件
ros2 bag play chatter_data_2026_01_25_10_05_00/

# 订阅节点终端会实时显示"收到消息:Hello ROS2! Count: x",和录制时一致
步骤 4:过滤提取数据
复制代码
# 提取/chatter中Count>5的消息,生成新Bag
ros2 bag filter chatter_data_2026_01_25_10_05_00/ count_gt5_bag "topic == '/chatter' and int(msg.data.split('Count: ')[1]) > 5"

# 回放新Bag,验证只包含Count>5的消息
ros2 bag play count_gt5_bag/
常见问题排查
  1. 录制后 Bag 文件为空 / 无数据

    • 检查录制的话题是否有数据发布(ros2 topic echo /话题名 验证);
    • 避免录制不存在的话题(ros2 topic list 确认话题活跃);
    • 录制时不要过早按 Ctrl+C(至少等待 1 秒,确保数据写入)。
  2. 回放时节点收不到数据

    • 检查回放的话题名是否和节点订阅的一致(用 ros2 topic list 确认);
    • 回放时添加 --remap 修正话题名(如 /scan 改为 /demo/scan);
    • 检查 Bag 文件是否包含该话题(ros2 bag info 验证)。
  3. Bag 文件过大

    • 避免用 -a 录制所有话题,按需指定话题名;
    • 录制时启用压缩(--compression zstd);
    • ros2 bag filter 提取关键数据,删除冗余内容。
  4. 回放速度异常(过快 / 过慢)

    • --rate 调整回放速度(如 --rate 1.0 恢复原速度);
    • 检查系统时间是否同步(Bag 依赖精确时间戳)。
总结
核心知识点
  1. ROS2 Bag 是话题数据的录制 / 回放工具,仅支持话题(不支持服务 / 动作),文件格式为 SQLite(.db3);
  2. 录制核心:按需指定话题(避免 -a)、用 -o 命名文件、启用压缩降低大小;
  3. 回放核心:支持速度调整、话题重映射、时间偏移,可精准复现录制场景;
  4. 进阶操作:用 filter 提取关键数据,减小文件体积。
高频命令
  1. 录制:ros2 bag record -o <文件名> <话题1> <话题2>
  2. 查看信息:ros2 bag info <Bag目录>
  3. 回放:ros2 bag play <Bag目录>(常用参数:--rate/--topics/--loop);
  4. 过滤:ros2 bag filter <输入> <输出> <过滤表达式>
关键使用场景
  1. 复现问题:录制故障场景的话题数据,离线回放调试;
  2. 算法调试:录制传感器数据(激光 / 相机),离线测试导航 / 感知算法;
  3. 数据分享:将 Bag 文件发给同事,无需现场即可复现场景。

11.urdf

URDF 核心认知

URDF 是基于 XML 的机器人描述格式,核心作用是将机器人拆解为 "连杆(Link)" 和 "关节(Joint)",定义:

  • 📏 几何属性:每个连杆的形状(长方体 / 圆柱 / 球体)、尺寸、外观(颜色 / 材质);
  • 📍 位置关系:关节连接的两个连杆的相对位置、旋转关系;
  • ⚖️ 物理属性:连杆的质量、惯性矩阵,关节的运动范围(限位)、阻尼等;
  • 🔌 传感器挂载:激光雷达、相机等传感器在连杆上的安装位置。
URDF 解决的核心问题

机器人是由多个部件组成的刚体系统,URDF 把 "物理机器人" 抽象为 "连杆 - 关节树",让 ROS2 能:

  1. 在 RViz2 中可视化机器人模型;
  2. 在 Gazebo 中进行物理仿真;
  3. 计算运动学 / 动力学(如逆运动学求解、碰撞检测);
  4. 定位传感器相对于机器人基坐标的位置(结合 TF2)。
URDF 关键知识点
1. URDF 的核心结构(XML 节点)

URDF 文件的核心是 <robot> 根节点,包含 <link>(连杆)和 <joint>(关节)两类子节点,结构如下:

复制代码
<?xml version="1.0"?>
<robot name="my_robot">  <!-- 机器人名称(唯一) -->
  <!-- 1. 定义连杆:base_link(机器人基连杆) -->
  <link name="base_link">
    <!-- 几何形状 -->
    <visual>
      <geometry><box size="0.2 0.1 0.1"/></geometry>  <!-- 长方体:x/y/z尺寸 -->
      <material name="blue"><color rgba="0 0 1 1"/></material>  <!-- 颜色:RGBA(透明率1) -->
    </visual>
    <!-- 碰撞检测(简化几何,提升效率) -->
    <collision>
      <geometry><box size="0.2 0.1 0.1"/></geometry>
    </collision>
    <!-- 物理属性 -->
    <inertial>
      <mass value="1.0"/>  <!-- 质量(kg) -->
      <inertia ixx="0.01" ixy="0.0" ixz="0.0" iyy="0.01" iyz="0.0" izz="0.01"/>  <!-- 惯性矩阵 -->
    </inertial>
  </link>

  <!-- 2. 定义连杆:laser_link(激光雷达连杆) -->
  <link name="laser_link">
    <visual>
      <geometry><cylinder length="0.05" radius="0.02"/></geometry>  <!-- 圆柱:长度+半径 -->
      <material name="red"><color rgba="1 0 0 1"/></material>
    </visual>
  </link>

  <!-- 3. 定义关节:连接base_link和laser_link -->
  <joint name="base_to_laser" type="fixed">  <!-- fixed:固定关节(无运动) -->
    <parent link="base_link"/>  <!-- 父连杆 -->
    <child link="laser_link"/>  <!-- 子连杆 -->
    <!-- 子连杆相对于父连杆的位置(平移+旋转) -->
    <origin xyz="0.1 0 0.05" rpy="0 0 0"/>  <!-- xyz:平移,rpy:欧拉角(弧度) -->
  </joint>
</robot>
2. 核心节点详解
节点 子节点 作用
<robot> - 根节点,name 属性为机器人名称(唯一)
<link> <visual> 定义连杆的可视化外观(形状、颜色),仅用于显示,不影响物理仿真
<collision> 定义连杆的碰撞几何(简化形状),用于碰撞检测 / 仿真
<inertial> 定义连杆的质量、惯性矩阵,用于动力学仿真(Gazebo 必需)
<joint> <parent>/<child> 定义关节连接的父 / 子连杆(必须存在)
<origin> 子连杆相对于父连杆的位姿(xyz:平移,rpy:旋转)
<limit> 运动关节的限位(如旋转角度范围、速度限制)
3. 关节类型
关节类型 特点 适用场景
fixed 固定关节,无运动 传感器 / 固定部件(如激光雷达安装)
revolute 旋转关节,单轴旋转 车轮、机械臂关节(有角度限位)
prismatic 移动关节,单轴平移 线性滑轨、升降台
continuous 连续旋转关节,无角度限位 电机轴、无刷电机
4. URDF 与 TF2 的关系

URDF 定义的 "连杆 - 关节树" 会自动生成 TF 变换树:

  • 每个 <link> 对应一个 TF 坐标系;
  • 每个 <joint><origin> 定义了父子连杆的 TF 变换;
  • 启动机器人状态发布器(robot_state_publisher)后,会自动广播 URDF 中的 TF 变换,无需手动编写 TF 广播代码。
5. URDF 的局限性与替代方案
  • 局限性:XML 格式繁琐,复杂机器人编写易出错;不支持模块化复用(如重复连杆需重复定义);

  • 替代方案

    • Xacro:URDF 的升级版(XML 宏),支持变量、宏定义、条件判断,大幅简化复杂机器人描述(新手推荐);
    • SDF:Gazebo 原生格式,支持更丰富的物理属性(如摩擦力、弹性),可与 URDF 转换。
URDF 常用命令
1. 检查 URDF 语法正确性

编写 URDF 后首先验证语法,避免可视化 / 仿真时报错:

复制代码
# 通用格式:检查URDF文件语法
check_urdf <urdf文件路径>

# 示例:检查my_robot.urdf
check_urdf my_robot.urdf

# 输出说明:
# - 若语法正确:显示"robot name is: my_robot"+连杆/关节列表;
# - 若语法错误:提示具体错误位置(如缺少</link>标签)。
2. 生成 URDF 变换树(可视化连杆 - 关节关系)
复制代码
# 通用格式:生成URDF的变换树(.dot文件)
urdf_to_graphiz <urdf文件路径>

# 示例:生成my_robot.urdf的变换树
urdf_to_graphiz my_robot.urdf

# 生成my_robot.pdf文件,查看连杆-关节树:
evince my_robot.pdf
3. 在 RViz2 中可视化 URDF

需结合 robot_state_publisher 发布 URDF 模型,步骤:

复制代码
# 1. 启动RViz2+机器人状态发布器(加载URDF文件)
ros2 launch urdf_tutorial display.launch.py model:=/path/to/my_robot.urdf

# 2. 或手动启动(分步):
# 终端1:启动机器人状态发布器(发布URDF和TF)
ros2 run robot_state_publisher robot_state_publisher --ros-args -p robot_description:="$(cat /path/to/my_robot.urdf)"

# 终端2:启动RViz2
rviz2

# RViz2配置:
# - Fixed Frame 设置为 base_link;
# - Displays → Add → RobotModel;
# - RobotModel 的 Robot Description 填写 /robot_description;
# 即可看到机器人模型可视化效果。
4. 将 URDF 转换为 SDF(Gazebo 仿真)

Gazebo 优先支持 SDF 格式,可将 URDF 转换为 SDF:

复制代码
# 通用格式:URDF转SDF
gz sdf -p <urdf文件路径> > <输出sdf文件>

# 示例:
gz sdf -p my_robot.urdf > my_robot.sdf
5. Xacro 编译为 URDF(新手推荐)

Xacro 是 URDF 的简化版,需先编译为 URDF 才能使用:

复制代码
# 通用格式:Xacro编译为URDF
xacro <xacro文件路径> > <输出urdf文件>

# 示例:
xacro my_robot.xacro > my_robot.urdf
实操示例
步骤 1:编写简单 URDF 文件

创建 my_robot.urdf 文件,内容为上文的核心结构示例(base_link + laser_link + fixed 关节)。

步骤 2:验证 URDF 语法
复制代码
check_urdf my_robot.urdf
# 输出应包含:
# robot name is: my_robot
# -------- Successfully Parsed XML ---------------
# Link base_link:
#     Links: 
#         base_link (parent) -> laser_link (child)
#     Joints: base_to_laser
步骤 3:生成变换树
复制代码
urdf_to_graphiz my_robot.urdf
evince my_robot.pdf
# 能看到 base_link → laser_link 的固定关节关系。
步骤 4:RViz2 可视化
复制代码
# 启动机器人状态发布器
ros2 run robot_state_publisher robot_state_publisher --ros-args -p robot_description:="$(cat my_robot.urdf)"

# 启动RViz2,配置后能看到:
# - 蓝色长方体(base_link);
# - 红色圆柱(laser_link)在base_link前方0.1m、上方0.05m处。
常见问题排查
  1. RViz2 中看不到机器人模型

    • 检查 Fixed Frame 是否设置为 URDF 中的根连杆(如 base_link);
    • 检查 robot_state_publisher 是否正确加载 URDF(ros2 param get /robot_state_publisher robot_description 验证);
    • 检查 URDF 语法(check_urdf),确保无语法错误。
  2. check_urdf 报错 "Invalid XML"

    • 检查 XML 标签是否闭合(如 <link> 必须有 </link>);
    • 检查属性值是否合法(如 xyz 是 3 个数字,用空格分隔);
    • 避免中文 / 特殊字符(URDF 仅支持 ASCII 字符)。
  3. Gazebo 仿真中机器人 "飘起来"

    • 缺少 <inertial> 节点(Gazebo 必需质量 / 惯性矩阵);
    • 惯性矩阵设置不合理(如质量为 0,或惯性值过大 / 过小)。
  4. 关节运动异常(如旋转超出限位)

    • 检查 <joint><limit> 节点(如 lower/upper 定义角度范围);
    • 关节类型错误(如需旋转用 revolute,而非 fixed)。
总结
核心知识点
  1. URDF 是 ROS2 描述机器人的 XML 格式,核心是 <link>(连杆)和 <joint>(关节),定义机器人的几何 / 物理 / 位姿属性;
  2. 关节类型中 fixed(固定)最常用(传感器安装),revolute(旋转)用于运动关节;
  3. URDF 会自动生成 TF 变换树,通过 robot_state_publisher 广播 TF 变换;
  4. Xacro 是 URDF 的简化版,支持变量 / 宏,大幅降低编写复杂度(新手优先用 Xacro)。
高频命令
  1. 语法检查:check_urdf <urdf文件>(编写后第一步);
  2. 变换树生成:urdf_to_graphiz <urdf文件>(可视化连杆 - 关节关系);
  3. 可视化:ros2 run robot_state_publisher robot_state_publisher --ros-args -p robot_description:="$(cat <urdf文件>)" + RViz2;
  4. Xacro 编译:xacro <xacro文件> > <urdf文件>
关键使用流程
  1. 编写 URDF/Xacro 文件 → 2. check_urdf 验证语法 → 3. urdf_to_graphiz 查看变换树 → 4. RViz2 可视化调试 → 5. 转换为 SDF 用于 Gazebo 仿真

12.xacro

Xacro 核心认知
1. 什么是 Xacro?

Xacro(XML Macros)是为 URDF 设计的预处理工具,本质是 "带语法增强的 XML":

  • ✅ 支持变量定义 :用变量替代硬编码的尺寸 / 质量(如 <xacro:property name="base_length" value="0.2"/>);
  • ✅ 支持宏定义:复用重复的连杆 / 关节(如多个车轮只需定义一次宏);
  • ✅ 支持数学运算 :直接计算尺寸(如 $(arg wheel_radius) * 2);
  • ✅ 支持条件判断:根据参数生成不同模型(如是否加载传感器);
  • ✅ 最终输出:Xacro 文件需编译为标准 URDF 文件,才能被 ROS2 识别(xacro xxx.xacro > xxx.urdf)。
2. Xacro 与 URDF 的核心区别
特性 URDF Xacro
变量 / 参数 无,硬编码所有数值 支持变量、参数、数学运算
代码复用 无,重复连杆需重复写 宏定义(macro)一键复用
逻辑控制 支持条件判断、循环(进阶)
文件体积 大(冗余代码多) 小(模块化、复用性强)
使用方式 直接使用 先编译为 URDF 再使用
3. Xacro 的核心价值
  • 简化编写:复杂机器人(如多轮小车、机械臂)的代码量减少 50% 以上;
  • 易于维护:修改尺寸 / 参数只需改变量,无需逐行修改所有硬编码值;
  • 模块化 :可拆分多个 Xacro 文件(如 base.xacrolaser.xacro),通过 <xacro:include> 组合,降低耦合。
Xacro 核心语法

Xacro 基于 XML 扩展,核心语法都以 xacro: 前缀标识,以下是新手最常用的 5 个核心特性:

1. 变量定义(property)
作用

定义可复用的变量(如尺寸、质量、颜色),替代 URDF 中的硬编码数值,便于统一修改。

语法
复制代码
<!-- 基础变量定义:name=变量名,value=值(数值/字符串/列表) -->
<xacro:property name="base_length" value="0.2"/>    <!-- 数值 -->
<xacro:property name="base_width" value="0.1"/>
<xacro:property name="base_height" value="0.1"/>
<xacro:property name="base_color" value="0 0 1 1"/> <!-- RGBA颜色 -->

<!-- 数学运算:用${}包裹表达式,支持+、-、*、/、() -->
<xacro:property name="base_volume" value="${base_length * base_width * base_height}"/>
使用方式
复制代码
<!-- 在URDF节点中引用变量:${变量名} -->
<link name="base_link">
  <visual>
    <geometry><box size="${base_length} ${base_width} ${base_height}"/></geometry>
    <material name="blue"><color rgba="${base_color}"/></material>
  </visual>
</link>
2. 宏定义(macro)
作用

定义可复用的 "代码块"(如重复的连杆、关节),只需写一次,多次调用(核心!)。

语法
复制代码
<!-- 定义宏:name=宏名,params=参数列表(多个参数用空格分隔) -->
<xacro:macro name="wheel_link" params="wheel_name wheel_radius wheel_mass">
  <link name="${wheel_name}">
    <visual>
      <geometry><cylinder length="0.05" radius="${wheel_radius}"/></geometry>
      <material name="gray"><color rgba="0.5 0.5 0.5 1"/></material>
    </visual>
    <inertial>
      <mass value="${wheel_mass}"/>
      <inertia ixx="0.001" ixy="0.0" ixz="0.0" iyy="0.001" iyz="0.0" izz="0.001"/>
    </inertial>
  </link>
</xacro:macro>

<!-- 调用宏:传入参数(顺序/键值对均可) -->
<xacro:wheel_link wheel_name="left_wheel" wheel_radius="0.05" wheel_mass="0.2"/>
<xacro:wheel_link wheel_name="right_wheel" wheel_radius="0.05" wheel_mass="0.2"/>
3. 文件包含(include)
作用

拆分大型 Xacro 文件为多个模块化文件(如底盘、传感器、机械臂),便于维护和复用。

语法
复制代码
<!-- 1. 包含同目录下的Xacro文件 -->
<xacro:include filename="base.xacro"/>
<xacro:include filename="laser.xacro"/>

<!-- 2. 包含其他目录的Xacro文件(绝对/相对路径) -->
<xacro:include filename="$(find my_robot_description)/urdf/sensors/imu.xacro"/>

<!-- 3. 包含时传递参数(给被包含文件的变量赋值) -->
<xacro:include filename="laser.xacro">
  <xacro:arg name="laser_x" value="0.1"/>  <!-- 激光雷达x轴偏移 -->
  <xacro:arg name="laser_z" value="0.05"/> <!-- 激光雷达z轴偏移 -->
</xacro:include>
4. 参数传递(arg)
作用

定义 "外部可配置的参数",编译 Xacro 时可通过命令行传入,实现 "一份模型,多套配置"。

语法
复制代码
<!-- 1. 定义参数(可指定默认值) -->
<xacro:arg name="robot_name" default="my_robot"/>
<xacro:arg name="has_laser" default="true"/> <!-- 布尔值:是否加载激光雷达 -->

<!-- 2. 引用参数(和变量用法一致:${参数名}) -->
<robot name="${robot_name}">
  <!-- 3. 条件判断:根据参数决定是否加载激光雷达(进阶) -->
  <xacro:if value="${has_laser}">
    <xacro:include filename="laser.xacro"/>
  </xacro:if>
</robot>
编译时传参
复制代码
# 编译时覆盖默认参数:不加载激光雷达,机器人名改为"small_robot"
xacro my_robot.xacro robot_name:=small_robot has_laser:=false > my_robot.urdf
5. 数学运算与函数
核心运算

Xacro 支持基本数学运算和常用函数,直接在 ${} 中使用:

复制代码
<!-- 基础运算:+ - * / () -->
<xacro:property name="wheel_diameter" value="${wheel_radius * 2}"/>
<xacro:property name="half_length" value="${base_length / 2}"/>

<!-- 常用函数(ROS2 内置) -->
<xacro:property name="pi" value="${3.1415926}"/>
<xacro:property name="angle_rad" value="${deg2rad(90)}"/> <!-- 角度转弧度 -->
<xacro:property name="angle_deg" value="${rad2deg(1.57)}"/> <!-- 弧度转角度 -->
<xacro:property name="abs_value" value="${abs(-0.1)}"/>     <!-- 绝对值 -->
<xacro:property name="max_value" value="${max(0.1, 0.2)}"/> <!-- 最大值 -->
Xacro 实操示例
示例:编写两轮小车的 Xacro 文件

创建 two_wheeled_robot.xacro,包含变量、宏、参数、条件判断:

复制代码
<?xml version="1.0"?>
<robot name="two_wheeled_robot" xmlns:xacro="http://www.ros.org/wiki/xacro">
  <!-- 1. 定义全局变量 -->
  <xacro:property name="base_length" value="0.2"/>
  <xacro:property name="base_width" value="0.15"/>
  <xacro:property name="base_height" value="0.1"/>
  <xacro:property name="wheel_radius" value="0.05"/>
  <xacro:property name="wheel_width" value="0.03"/>
  <xacro:property name="wheel_mass" value="0.2"/>

  <!-- 2. 定义参数:是否加载激光雷达(默认加载) -->
  <xacro:arg name="has_laser" default="true"/>

  <!-- 3. 定义宏:车轮连杆+关节(复用两次) -->
  <xacro:macro name="wheel" params="wheel_name parent_link x_offset y_offset">
    <!-- 车轮连杆 -->
    <link name="${wheel_name}">
      <visual>
        <geometry><cylinder length="${wheel_width}" radius="${wheel_radius}"/></geometry>
        <material name="gray"><color rgba="0.5 0.5 0.5 1"/></material>
      </visual>
      <inertial>
        <mass value="${wheel_mass}"/>
        <inertia ixx="0.001" ixy="0.0" ixz="0.0" iyy="0.001" iyz="0.0" izz="0.001"/>
      </inertial>
    </link>
    <!-- 车轮关节(连续旋转关节) -->
    <joint name="${parent_link}_to_${wheel_name}" type="continuous">
      <parent link="${parent_link}"/>
      <child link="${wheel_name}"/>
      <origin xyz="${x_offset} ${y_offset} -${base_height/2}" rpy="${deg2rad(90)} 0 0"/>
      <axis xyz="0 1 0"/> <!-- 旋转轴:y轴 -->
    </joint>
  </xacro:macro>

  <!-- 4. 定义底盘连杆 -->
  <link name="base_link">
    <visual>
      <geometry><box size="${base_length} ${base_width} ${base_height}"/></geometry>
      <material name="blue"><color rgba="0 0 1 1"/></material>
    </visual>
    <inertial>
      <mass value="1.0"/>
      <inertia ixx="0.01" ixy="0.0" ixz="0.0" iyy="0.01" iyz="0.0" izz="0.01"/>
    </inertial>
  </link>

  <!-- 5. 调用宏:创建左右车轮 -->
  <xacro:wheel wheel_name="left_wheel" parent_link="base_link" x_offset="0" y_offset="${base_width/2 + wheel_width/2}"/>
  <xacro:wheel wheel_name="right_wheel" parent_link="base_link" x_offset="0" y_offset="-${base_width/2 - wheel_width/2}"/>

  <!-- 6. 条件判断:加载激光雷达 -->
  <xacro:if value="${has_laser}">
    <link name="laser_link">
      <visual>
        <geometry><cylinder length="0.05" radius="0.02"/></geometry>
        <material name="red"><color rgba="1 0 0 1"/></material>
      </visual>
    </link>
    <joint name="base_to_laser" type="fixed">
      <parent link="base_link"/>
      <child link="laser_link"/>
      <origin xyz="${base_length/2} 0 ${base_height/2}" rpy="0 0 0"/>
    </joint>
  </xacro:if>
</robot>
编译与验证
复制代码
# 1. 编译Xacro为URDF(使用默认参数:加载激光雷达)
xacro two_wheeled_robot.xacro > two_wheeled_robot.urdf

# 2. 编译时传参:不加载激光雷达
xacro two_wheeled_robot.xacro has_laser:=false > two_wheeled_robot_no_laser.urdf

# 3. 验证URDF语法(确保无错误)
check_urdf two_wheeled_robot.urdf

# 4. RViz2可视化
ros2 run robot_state_publisher robot_state_publisher --ros-args -p robot_description:="$(cat two_wheeled_robot.urdf)"
rviz2  # 配置Fixed Frame=base_link,添加RobotModel查看
Xacro 常用技巧
1. 模块化拆分(大型模型必备)

将复杂模型拆分为多个 Xacro 文件,通过 <xacro:include> 组合,便于维护:

复制代码
# 目录结构
my_robot/
├── urdf/
│   ├── base.xacro       # 底盘
│   ├── wheels.xacro     # 车轮
│   ├── sensors/
│   │   ├── laser.xacro  # 激光雷达
│   │   └── camera.xacro # 相机
│   └── my_robot.xacro   # 主文件(包含所有子文件)

主文件 my_robot.xacro

复制代码
<robot name="my_robot" xmlns:xacro="http://www.ros.org/wiki/xacro">
  <xacro:include filename="base.xacro"/>
  <xacro:include filename="wheels.xacro"/>
  <xacro:include filename="sensors/laser.xacro"/>
  <xacro:include filename="sensors/camera.xacro"/>
</robot>
2. 循环生成重复部件(进阶)

<xacro:for> 循环生成多个相同部件(如 6 轮小车的车轮):

复制代码
<!-- 循环生成6个车轮(xacro 1.12+支持) -->
<xacro:property name="wheel_positions" value="[-0.1, -0.05, 0, 0.05, 0.1, 0.15]"/>
<xacro:for item="y_offset" in="${wheel_positions}">
  <xacro:wheel wheel_name="wheel_${loop.index}" parent_link="base_link" x_offset="0" y_offset="${y_offset}"/>
</xacro:for>
3. 与 Launch 文件结合(自动编译)

无需手动编译 Xacro,在 Launch 文件中直接加载 Xacro 并传递参数:

复制代码
from launch_ros.actions import Node
from launch.substitutions import LaunchConfiguration
from launch.actions import DeclareLaunchArgument
import xacro
import os

def generate_launch_description():
    # 1. 声明参数
    has_laser_arg = DeclareLaunchArgument(
        'has_laser',
        default_value='true',
        description='是否加载激光雷达'
    )

    # 2. 加载Xacro文件并编译
    xacro_path = os.path.join('demo_py_node', 'urdf', 'two_wheeled_robot.xacro')
    robot_description_config = xacro.process_file(
        xacro_path,
        mappings={'has_laser': LaunchConfiguration('has_laser')}  # 传递参数
    )
    robot_description = {'robot_description': robot_description_config.toxml()}

    # 3. 启动机器人状态发布器
    robot_state_pub_node = Node(
        package='robot_state_publisher',
        executable='robot_state_publisher',
        parameters=[robot_description],
        output='screen'
    )

    return LaunchDescription([
        has_laser_arg,
        robot_state_pub_node
    ])

运行 Launch 文件(传参):

复制代码
ros2 launch demo_py_node robot_launch.py has_laser:=false
4. 常用调试命令
复制代码
# 1. 查看Xacro编译后的URDF内容(调试用)
xacro two_wheeled_robot.xacro --inorder

# 2. 检查Xacro语法(ROS2 Humble+支持)
xacro --check two_wheeled_robot.xacro

# 3. 编译时输出调试信息
xacro two_wheeled_robot.xacro --verbose > two_wheeled_robot.urdf
常见问题排查
  1. 编译报错 "undefined variable/arg"
  • 检查变量 / 参数名是否拼写错误(区分大小写,如 base_lengthBase_Length);
  • 确认变量 / 参数在使用前已定义(Xacro 按顺序解析,先定义后使用);
  • 包含文件时,确认参数已传递(如 <xacro:arg> 需在 <xacro:include> 中赋值)。
  1. 数学运算报错 "invalid expression"
  • 确保运算表达式在 ${} 内(如 ${a + b},而非 $a + b);
  • 避免除以 0、非法函数调用(如 deg2rad("90") 字符串转角度,需用数值 deg2rad(90));
  • 复杂运算拆分(如 ${(a + b) * c} 加括号明确优先级)。
  1. RViz2 中模型显示异常
  • 编译后的 URDF 语法错误(先用 check_urdf 验证);
  • 宏参数传递错误(如车轮偏移量计算错误导致位置异常);
  • 缺少 xmlns:xacro 命名空间(根节点必须加 xmlns:xacro="http://www.ros.org/wiki/xacro")。
  1. 条件判断不生效
  • 布尔值需用 ${true}/${false},而非 true/false(如 <xacro:if value="${has_laser}">);
  • 确认参数传递正确(编译时传参需用 :=,而非 =)。
总结
核心知识点
  1. Xacro 是 URDF 的增强版,核心特性:变量(property)、宏(macro)、参数(arg)、数学运算、条件判断
  2. Xacro 需编译为 URDF 才能使用,编译命令:xacro xxx.xacro [参数名:=值] > xxx.urdf
  3. 模块化拆分是编写复杂模型的核心技巧,通过 <xacro:include> 组合子文件;
  4. 根节点必须添加 Xacro 命名空间:xmlns:xacro="http://www.ros.org/wiki/xacro"
必记语法清单
语法 作用 示例
<xacro:property> 定义变量 <xacro:property name="r" value="0.05"/>
<xacro:macro> 定义复用宏 <xacro:macro name="wheel" params="name r"/>
<xacro:arg> 定义外部参数 <xacro:arg name="has_laser" default="true"/>
<xacro:include> 包含其他 Xacro 文件 <xacro:include filename="laser.xacro"/>
${} 引用变量 / 运算 ${r * 2}${deg2rad(90)}
<xacro:if> 条件判断 <xacro:if value="${has_laser}">
关键使用流程
  1. 拆分模块化 Xacro 文件 → 2. 定义变量 / 宏 / 参数 → 3. 编译为 URDF 并验证语法 → 4. Launch 文件自动加载(可选)→ 5. RViz2 可视化调试。
常用模板
0.通用前置模板(所有文件必加)

所有 Xacro 文件开头需添加以下内容,定义通用变量和命名空间:

复制代码
<?xml version="1.0"?>
<robot name="robot_modules" xmlns:xacro="http://www.ros.org/wiki/xacro">
  <!-- 通用数学常量 -->
  <xacro:property name="pi" value="${3.1415926}"/>
  <!-- 通用颜色定义 -->
  <xacro:property name="color_blue" value="0 0 1 1"/>
  <xacro:property name="color_red" value="1 0 0 1"/>
  <xacro:property name="color_gray" value="0.5 0.5 0.5 1"/>
  <xacro:property name="color_black" value="0 0 0 1"/>
  <xacro:property name="color_white" value="1 1 1 1"/>

  <!-- ========== 以下添加具体模块模板 ========== -->

</robot>
1. 基础底盘模板(Box 型)

适用于轮式小车、移动机器人的基础底盘,支持自定义尺寸 / 质量:

复制代码
<!-- 宏:基础底盘连杆 -->
<xacro:macro name="base_link" params="name length width height mass color">
  <link name="${name}">
    <!-- 可视化属性 -->
    <visual>
      <geometry>
        <box size="${length} ${width} ${height}"/>
      </geometry>
      <material name="base_material">
        <color rgba="${color}"/>
      </material>
      <!-- 底盘原点在几何中心 -->
      <origin xyz="0 0 0" rpy="0 0 0"/>
    </visual>

    <!-- 碰撞属性(与可视化一致) -->
    <collision>
      <geometry>
        <box size="${length} ${width} ${height}"/>
      </geometry>
      <origin xyz="0 0 0" rpy="0 0 0"/>
    </collision>

    <!-- 惯性属性(简化版,适用于规则立方体) -->
    <inertial>
      <mass value="${mass}"/>
      <inertia 
        ixx="${mass * (width*width + height*height)/12}"
        ixy="0.0"
        ixz="0.0"
        iyy="${mass * (length*length + height*height)/12}"
        iyz="0.0"
        izz="${mass * (length*length + width*width)/12}"
      />
    </inertial>
  </link>
</xacro:macro>

<!-- 调用示例:创建base_link底盘 -->
<xacro:base_link 
  name="base_link" 
  length="0.3"    <!-- x轴长度(m) -->
  width="0.2"     <!-- y轴宽度(m) -->
  height="0.1"    <!-- z轴高度(m) -->
  mass="1.5"      <!-- 质量(kg) -->
  color="${color_blue}"
/>
2. 车轮模板(圆柱型,支持连续旋转)

适用于差速轮、阿克曼轮,包含连杆 + 关节,支持自定义安装位置:

复制代码
<!-- 宏:车轮(连杆+关节) -->
<xacro:macro name="wheel" params="wheel_name parent_link x_offset y_offset z_offset radius width mass">
  <!-- 1. 车轮连杆 -->
  <link name="${wheel_name}">
    <visual>
      <geometry>
        <cylinder length="${width}" radius="${radius}"/>
      </geometry>
      <material name="wheel_material">
        <color rgba="${color_gray}"/>
      </material>
      <!-- 车轮旋转轴对齐y轴 -->
      <origin xyz="0 0 0" rpy="${pi/2} 0 0"/>
    </visual>

    <collision>
      <geometry>
        <cylinder length="${width}" radius="${radius}"/>
      </geometry>
      <origin xyz="0 0 0" rpy="${pi/2} 0 0"/>
    </collision>

    <inertial>
      <mass value="${mass}"/>
      <!-- 圆柱惯性矩阵(简化版) -->
      <inertia 
        ixx="${mass*(3*radius*radius + width*width)/12}"
        ixy="0.0"
        ixz="0.0"
        iyy="${mass*(3*radius*radius + width*width)/12}"
        iyz="0.0"
        izz="${mass*radius*radius/2}"
      />
    </inertial>
  </link>

  <!-- 2. 车轮关节(连续旋转,无角度限位) -->
  <joint name="${parent_link}_to_${wheel_name}" type="continuous">
    <parent link="${parent_link}"/>
    <child link="${wheel_name}"/>
    <!-- 车轮安装位置(相对于父连杆) -->
    <origin xyz="${x_offset} ${y_offset} ${z_offset}" rpy="0 0 0"/>
    <!-- 旋转轴:y轴(车轮绕y轴旋转) -->
    <axis xyz="0 1 0"/>
    <!-- 关节阻尼(仿真用) -->
    <dynamics damping="0.01" friction="0.01"/>
  </joint>
</xacro:macro>

<!-- 调用示例:创建左右车轮 -->
<!-- 左车轮:y轴正方向偏移 -->
<xacro:wheel 
  wheel_name="left_wheel" 
  parent_link="base_link" 
  x_offset="0"          <!-- x轴偏移 -->
  y_offset="0.1"        <!-- y轴偏移(底盘宽度/2) -->
  z_offset="-0.05"      <!-- z轴偏移(底盘高度/2,接地) -->
  radius="0.06"         <!-- 车轮半径(m) -->
  width="0.03"          <!-- 车轮宽度(m) -->
  mass="0.3"            <!-- 车轮质量(kg) -->
/>

<!-- 右车轮:y轴负方向偏移 -->
<xacro:wheel 
  wheel_name="right_wheel" 
  parent_link="base_link" 
  x_offset="0"          
  y_offset="-0.1"       
  z_offset="-0.05"      
  radius="0.06"         
  width="0.03"          
  mass="0.3"            
/>
3. 激光雷达模板(2D/3D LiDAR)

适用于常见激光雷达(如思岚 A1、速腾 16 线),固定关节安装:

复制代码
<!-- 宏:激光雷达 -->
<xacro:macro name="lidar" params="name parent_link x_offset y_offset z_offset roll pitch yaw">
  <!-- 1. 激光雷达连杆 -->
  <link name="${name}">
    <visual>
      <geometry>
        <cylinder length="0.08" radius="0.04"/>
      </geometry>
      <material name="lidar_material">
        <color rgba="${color_red}"/>
      </material>
    </visual>

    <collision>
      <geometry>
        <cylinder length="0.08" radius="0.04"/>
      </geometry>
    </collision>

    <inertial>
      <mass value="0.2"/>
      <inertia 
        ixx="0.0001" ixy="0.0" ixz="0.0"
        iyy="0.0001" iyz="0.0"
        izz="0.0001"
      />
    </inertial>
  </link>

  <!-- 2. 激光雷达关节(固定) -->
  <joint name="${parent_link}_to_${name}" type="fixed">
    <parent link="${parent_link}"/>
    <child link="${name}"/>
    <origin xyz="${x_offset} ${y_offset} ${z_offset}" rpy="${roll} ${pitch} ${yaw}"/>
  </joint>
</xacro:macro>

<!-- 调用示例:安装思岚A1激光雷达 -->
<xacro:lidar 
  name="laser_link" 
  parent_link="base_link" 
  x_offset="0.15"    <!-- 底盘前方0.15m -->
  y_offset="0"       <!-- 中心位置 -->
  z_offset="0.08"    <!-- 离地0.08m -->
  roll="0" 
  pitch="0" 
  yaw="0"
/>
4. 相机模板(RGB-D / 单目)

适用于 USB 相机、深度相机(如 Intel Realsense),支持自定义安装角度:

复制代码
<!-- 宏:相机 -->
<xacro:macro name="camera" params="name parent_link x_offset y_offset z_offset roll pitch yaw">
  <!-- 1. 相机连杆 -->
  <link name="${name}">
    <visual>
      <geometry>
        <box size="0.04 0.06 0.03"/>
      </geometry>
      <material name="camera_material">
        <color rgba="${color_black}"/>
      </material>
    </visual>

    <collision>
      <geometry>
        <box size="0.04 0.06 0.03"/>
      </geometry>
    </collision>

    <inertial>
      <mass value="0.1"/>
      <inertia 
        ixx="0.00005" ixy="0.0" ixz="0.0"
        iyy="0.00005" iyz="0.0"
        izz="0.00005"
      />
    </inertial>
  </link>

  <!-- 2. 相机关节(固定) -->
  <joint name="${parent_link}_to_${name}" type="fixed">
    <parent link="${parent_link}"/>
    <child link="${name}"/>
    <origin xyz="${x_offset} ${y_offset} ${z_offset}" rpy="${roll} ${pitch} ${yaw}"/>
  </joint>
</xacro:macro>

<!-- 调用示例:安装前视相机(向下倾斜10度) -->
<xacro:camera 
  name="camera_link" 
  parent_link="base_link" 
  x_offset="0.18"        <!-- 底盘前方0.18m -->
  y_offset="0"           
  z_offset="0.1"         <!-- 离地0.1m -->
  roll="0" 
  pitch="${deg2rad(-10)}"<!-- 向下倾斜10度 -->
  yaw="0"
/>
5. IMU 模板(惯性测量单元)

适用于常见 IMU(如 MPU6050、BMI088),安装在机器人重心位置:

复制代码
<!-- 宏:IMU -->
<xacro:macro name="imu" params="name parent_link x_offset y_offset z_offset roll pitch yaw">
  <!-- 1. IMU连杆 -->
  <link name="${name}">
    <visual>
      <geometry>
        <box size="0.02 0.02 0.01"/>
      </geometry>
      <material name="imu_material">
        <color rgba="${color_white}"/>
      </material>
    </visual>

    <collision>
      <geometry>
        <box size="0.02 0.02 0.01"/>
      </geometry>
    </collision>

    <inertial>
      <mass value="0.01"/>
      <inertia 
        ixx="0.000001" ixy="0.0" ixz="0.0"
        iyy="0.000001" iyz="0.0"
        izz="0.000001"
      />
    </inertial>
  </link>

  <!-- 2. IMU关节(固定) -->
  <joint name="${parent_link}_to_${name}" type="fixed">
    <parent link="${parent_link}"/>
    <child link="${name}"/>
    <origin xyz="${x_offset} ${y_offset} ${z_offset}" rpy="${roll} ${pitch} ${yaw}"/>
  </joint>
</xacro:macro>

<!-- 调用示例:安装IMU在底盘中心 -->
<xacro:imu 
  name="imu_link" 
  parent_link="base_link" 
  x_offset="0"       <!-- 中心位置 -->
  y_offset="0"       
  z_offset="0.05"    <!-- 底盘顶部中心 -->
  roll="0" 
  pitch="0" 
  yaw="0"
/>
6. 机械臂单关节模板(旋转关节)

适用于 6 轴 / 7 轴机械臂的旋转关节,支持角度限位:

复制代码
<!-- 宏:机械臂旋转关节 -->
<xacro:macro name="arm_joint" params="joint_name link_name parent_link x_offset y_offset z_offset roll pitch yaw lower_limit upper_limit max_velocity max_effort">
  <!-- 1. 关节连杆 -->
  <link name="${link_name}">
    <visual>
      <geometry>
        <cylinder length="0.15" radius="0.03"/>
      </geometry>
      <material name="arm_material">
        <color rgba="${color_gray}"/>
      </material>
    </visual>

    <collision>
      <geometry>
        <cylinder length="0.15" radius="0.03"/>
      </geometry>
    </collision>

    <inertial>
      <mass value="0.5"/>
      <inertia 
        ixx="0.001" ixy="0.0" ixz="0.0"
        iyy="0.001" iyz="0.0"
        izz="0.001"
      />
    </inertial>
  </link>

  <!-- 2. 旋转关节(带角度限位) -->
  <joint name="${joint_name}" type="revolute">
    <parent link="${parent_link}"/>
    <child link="${link_name}"/>
    <origin xyz="${x_offset} ${y_offset} ${z_offset}" rpy="${roll} ${pitch} ${yaw}"/>
    <axis xyz="0 0 1"/> <!-- 绕z轴旋转 -->
    <!-- 角度限位 -->
    <limit 
      lower="${lower_limit}" 
      upper="${upper_limit}" 
      velocity="${max_velocity}" 
      effort="${max_effort}"
    />
    <dynamics damping="0.02" friction="0.02"/>
  </joint>
</xacro:macro>

<!-- 调用示例:创建机械臂第一关节 -->
<xacro:arm_joint 
  joint_name="joint1" 
  link_name="link1" 
  parent_link="base_link" 
  x_offset="0" 
  y_offset="0" 
  z_offset="0.1" 
  roll="0" 
  pitch="0" 
  yaw="0" 
  lower_limit="${deg2rad(-180)}"  <!-- 下限-180度 -->
  upper_limit="${deg2rad(180)}"   <!-- 上限180度 -->
  max_velocity="1.0"              <!-- 最大角速度(rad/s) -->
  max_effort="5.0"                <!-- 最大力矩(N·m) -->
/>
7. 脚轮模板(万向轮 / 从动轮)

适用于三轮 / 四轮小车的从动脚轮,固定关节无旋转:

复制代码
<!-- 宏:脚轮 -->
<xacro:macro name="caster" params="name parent_link x_offset y_offset z_offset">
  <!-- 1. 脚轮连杆 -->
  <link name="${name}">
    <visual>
      <geometry>
        <sphere radius="0.03"/>
      </geometry>
      <material name="caster_material">
        <color rgba="${color_black}"/>
      </material>
    </visual>

    <collision>
      <geometry>
        <sphere radius="0.03"/>
      </geometry>
    </collision>

    <inertial>
      <mass value="0.1"/>
      <inertia 
        ixx="0.00005" ixy="0.0" ixz="0.0"
        iyy="0.00005" iyz="0.0"
        izz="0.00005"
      />
    </inertial>
  </link>

  <!-- 2. 脚轮关节(固定) -->
  <joint name="${parent_link}_to_${name}" type="fixed">
    <parent link="${parent_link}"/>
    <child link="${name}"/>
    <origin xyz="${x_offset} ${y_offset} ${z_offset}" rpy="0 0 0"/>
  </joint>
</xacro:macro>

<!-- 调用示例:安装后脚轮 -->
<xacro:caster 
  name="caster_link" 
  parent_link="base_link" 
  x_offset="-0.15"   <!-- 底盘后方0.15m -->
  y_offset="0"       
  z_offset="-0.03"   <!-- 接地,略高于主动轮 -->
/>
8. 云台模板(两轴旋转)

适用于相机 / 激光雷达云台,支持俯仰 + 偏航旋转:

复制代码
<!-- 宏:云台偏航关节(绕z轴) -->
<xacro:macro name="pan_joint" params="name parent_link child_link x_offset y_offset z_offset">
  <joint name="${name}" type="continuous">
    <parent link="${parent_link}"/>
    <child link="${child_link}"/>
    <origin xyz="${x_offset} ${y_offset} ${z_offset}" rpy="0 0 0"/>
    <axis xyz="0 0 1"/>
    <dynamics damping="0.01" friction="0.01"/>
  </joint>
</xacro:macro>

<!-- 宏:云台俯仰关节(绕y轴) -->
<xacro:macro name="tilt_joint" params="name parent_link child_link x_offset y_offset z_offset lower upper">
  <joint name="${name}" type="revolute">
    <parent link="${parent_link}"/>
    <child link="${child_link}"/>
    <origin xyz="${x_offset} ${y_offset} ${z_offset}" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="${lower}" upper="${upper}" velocity="0.5" effort="2.0"/>
    <dynamics damping="0.01" friction="0.01"/>
  </joint>
</xacro:macro>

<!-- 调用示例:创建相机云台 -->
<!-- 1. 云台底座 -->
<link name="pan_link">
  <visual><geometry><cylinder length="0.05" radius="0.04"/></geometry><material name="gray"><color rgba="${color_gray}"/></material></visual>
  <collision><geometry><cylinder length="0.05" radius="0.04"/></geometry></collision>
  <inertial><mass value="0.2"/><inertia ixx="0.0001" ixy="0.0" ixz="0.0" iyy="0.0001" iyz="0.0" izz="0.0001"/></inertial>
</link>

<!-- 2. 偏航关节(360度旋转) -->
<xacro:pan_joint 
  name="pan_joint" 
  parent_link="base_link" 
  child_link="pan_link" 
  x_offset="0.1" y_offset="0" z_offset="0.1"/>

<!-- 3. 俯仰关节(-45~45度) -->
<xacro:tilt_joint 
  name="tilt_joint" 
  parent_link="pan_link" 
  child_link="camera_link" 
  x_offset="0" y_offset="0" z_offset="0.05"
  lower="${deg2rad(-45)}" upper="${deg2rad(45)}"/>

<!-- 4. 挂载相机 -->
<xacro:camera name="camera_link" parent_link="pan_link" x_offset="0" y_offset="0" z_offset="0" roll="0" pitch="0" yaw="0"/>
模板使用指南
1. 快速复用步骤
  1. 复制所需模块的宏定义到你的 Xacro 文件;
  2. 修改宏调用时的参数(如尺寸、安装位置、质量);
  3. 编译为 URDF 验证:xacro your_robot.xacro > your_robot.urdf
  4. check_urdf 检查语法,RViz2 可视化调试。
2. 模块化管理建议

将不同模块拆分到独立文件,通过 <xacro:include> 组合:

复制代码
urdf/
├── base.xacro       # 底盘
├── wheels.xacro     # 车轮+脚轮
├── sensors/
│   ├── lidar.xacro  # 激光雷达
│   ├── camera.xacro # 相机
│   └── imu.xacro    # IMU
├── arm.xacro        # 机械臂
└── robot.xacro      # 主文件(包含所有模块)
3. 适配 Gazebo 仿真

若需在 Gazebo 中使用,需添加以下扩展属性(示例:车轮):

复制代码
<!-- 在wheel宏的link节点内添加Gazebo属性 -->
<gazebo reference="${wheel_name}">
  <material>Gazebo/Gray</material>
  <turnGravityOff>false</turnGravityOff>
  <rollingFriction>0.01</rollingFriction>
  <slidingFriction>0.01</slidingFriction>
  <mu1>1.0</mu1>
  <mu2>1.0</mu2>
</gazebo>

<!-- 在wheel宏的joint节点内添加Gazebo传动(控制车轮) -->
<gazebo>
  <plugin name="wheel_controller" filename="libgazebo_ros_diff_drive.so">
    <ros>
      <namespace>/</namespace>
      <remapping>cmd_vel:=cmd_vel</remapping>
    </ros>
    <leftJoint>${parent_link}_to_left_wheel</leftJoint>
    <rightJoint>${parent_link}_to_right_wheel</rightJoint>
    <wheelSeparation>${width}</wheelSeparation>
    <wheelDiameter>${2*radius}</wheelDiameter>
    <maxVelocity>1.0</maxVelocity>
    <acceleration>0.5</acceleration>
  </plugin>
</gazebo>
总结
核心模板清单
  1. 底盘 + 车轮:移动机器人基础,适配差速小车;
  2. 激光雷达 + 相机 + IMU:常用传感器,固定关节安装;
  3. 机械臂关节 + 云台:适用于机械臂 / 云台类机器人;
  4. 脚轮:从动轮,适配三轮 / 四轮小车。
关键修改参数
模块 核心修改参数 示例值
底盘 length/width/height/mass 0.3/0.2/0.1/1.5
车轮 radius/width/mass/ 安装偏移 0.06/0.03/0.3
传感器 x_offset/y_offset/z_offset 0.15/0/0.08
关节 lower_limit/upper_limit -180°/180°
相关推荐
shangjian0074 分钟前
OpenClaw学习笔记-01-架构篇
笔记·学习·架构
少许极端5 分钟前
算法奇妙屋(三十四)-贪心算法学习之路 1
学习·算法·贪心算法
551只玄猫21 分钟前
【基于python的金融分析和风险管理 学习笔记】中阶篇 第6章 分析利率和汇率
笔记·python·学习·金融·学习笔记·汇率·利率
出门吃三碗饭24 分钟前
3DGS如何提升面向仿真的三维高斯重建质量
3d·机器人
孤独的小丑25 分钟前
OpenClaw学习资源汇编
汇编·学习
悠哉悠哉愿意33 分钟前
【单片机学习笔记】第十二届国赛经验复盘
笔记·单片机·嵌入式硬件·学习
zxm851336 分钟前
UV使用及UV与Anaconda的区别
大数据·学习·机器学习·uv
Shining059639 分钟前
AI 编译器系列(五)《拓展 Triton 深度学习编译器——DLCompiler》
人工智能·深度学习·学习·其他·架构·ai编译器·infinitensor
码农的小菜园41 分钟前
Java线程池学习笔记
java·笔记·学习
我不是程序猿儿1 小时前
【嵌入式】编码器计数倍频,机械一格与电气计数
stm32·单片机·嵌入式硬件·学习