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/String、sensor_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.总结
核心知识点
- 话题是异步松耦合 通信机制,核心是 "发布者 - 话题 - 订阅者" 模型,依赖统一的消息类型;
- QoS 是话题的通信规则,发布 / 订阅双方需匹配才能通信;
- 话题命名需规范,避免重名,高频数据建议用
BEST_EFFORTQoS。
高频命令
查: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()
总结
核心知识点
- 服务是同步请求 - 响应通信,适用于按需触发的指令交互,和话题的异步广播形成互补;
- 服务类型(srv)定义 "请求" 和 "响应" 格式,用
---分隔,客户端和服务端必须使用相同类型; - 服务名唯一,一个服务只能有一个服务端,客户端调用时会阻塞等待响应。
高频命令
查: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
总结
核心知识点
- 动作是为长时间任务 设计的通信机制,核心能力是实时反馈、任务取消、异步结果返回;
- 动作类型(
.action)包含 Goal(目标)、Feedback(反馈)、Result(结果)三部分,客户端和服务端必须使用相同类型; - 动作兼具话题的异步性和服务的交互性,是导航、机械臂运动等场景的首选通信方式。
高频命令
查: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.py 的 data_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_node、subscriber_node、count_action_server_node; - 所有节点的日志都会输出到当前终端(因
output='screen'); - 话题会被重映射为
/demo/chat,避免和其他节点冲突; - 若
publisher_node崩溃,会在 3 秒后自动重启。
Launch 进阶技巧
1. 嵌套引入其他 Launch 文件
如果有多个 Launch 文件(如 sensor_launch.py、nav_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
总结
核心知识点
- Launch 是 ROS2 批量启动节点的工具,推荐使用 Python 版(.launch.py),支持逻辑控制和动态配置;
- 核心组件是
Node(启动节点)、Remap(重映射)、Parameter(参数)、LaunchConfiguration(命令行参数); - 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 /tf或ros2 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
map是odom的父节点,odom是base_link的父节点;- 只能查询 "变换树中存在路径" 的两个坐标系(如
map↔laser可行,map↔imu需先添加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 广播器是否正常发布变换。
总结
核心知识点
- TF2 是 ROS2 处理坐标系变换的核心框架,分为静态 TF (固定变换)和动态 TF(实时变换);
- 所有坐标系组成变换树,必须无环、无孤立节点,才能查询任意两坐标系的变换;
- TF2 底层基于
/tf(动态)和/tf_static(静态)话题发布变换关系。
高频工具 / 命令
- 查变换树:
ros2 run tf2_tools view_frames+evince frames.pdf; - 查变换关系:
ros2 run tf2_ros tf2_echo 父坐标系 子坐标系; - 发布静态 TF:
ros2 run tf2_ros static_transform_publisher x y z yaw pitch roll 父 子; - 可视化:RViz2 中添加 TF 显示,设置 Fixed Frame 为全局坐标系。
开发核心
- 静态 TF:用
StaticTransformBroadcaster发布(只发一次); - 动态 TF:用
TransformBroadcaster+ 定时器周期性发布; - 查变换:用
Buffer+TransformListener的lookup_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_node和subscriber_node之间有连线(对应/chatter话题); - 连线标注
std_msgs/msg/String,表示通信的消息类型; - 验证节点是否正确建立通信(无连线则说明话题名 / 类型不匹配)。
2. rqt_plot(数值话题曲线绘制)
作用
实时绘制数值型话题 的曲线(如 std_msgs/msg/Int32、geometry_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.parameter 或 dynamic_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/,重新启动。
常见问题排查
-
rqt_graph 看不到节点:
- 检查节点是否运行(
ros2 node list); - 确保 ROS_DOMAIN_ID 一致(不同域 ID 的节点无法互通);
- 点击
Refresh刷新节点图。
- 检查节点是否运行(
-
rqt_plot 无法添加话题:
- 话题必须是数值型(如 String 类型无法绘制曲线);
- 话题路径填写正确(如
/chatter是 String 类型,需填/chatter/data); - 检查话题是否有数据发布(
ros2 topic echo验证)。
-
rqt_reconfigure 看不到节点:
- 节点未实现动态参数接口(需用
declare_parameter并设置dynamic_typing=True); - 节点未运行或名称拼写错误。
- 节点未实现动态参数接口(需用
总结
核心知识点
- RQT 是 ROS2 图形化调试框架,功能通过插件实现,替代终端命令提升调试效率;
- 核心插件与 ROS2 功能一一对应:
rqt_graph(节点关系)、rqt_plot(数值曲线)、rqt_topic(话题调试)、rqt_reconfigure(参数调整); - RQT 支持自定义布局、远程调试,是 ROS2 开发调试的必备工具。
高频工具优先级
- rqt_graph:优先掌握,验证节点通信是否正常;
- rqt_topic:一站式查看话题数据 / 频率,替代多个终端命令;
- rqt_plot:可视化数值数据,快速定位数据异常;
- rqt_console:集中查看日志,排查节点报错。
关键操作
- 启动方式:直接
rqt_xxx启动单个插件,或rqt启动主界面; - 核心逻辑:RQT 插件是终端命令的可视化封装,操作逻辑和终端命令一致;
- 调试流程:先用
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 核心界面组成
- Displays 面板(左侧,核心!):配置要显示的「数据类型」(如 TF、激光、机器人模型),所有可视化内容都需在这里添加 / 启用;
- 3D 可视化视图(中间,主区域):显示机器人、传感器数据、坐标系等核心内容,是调试的核心观察区;
- Views 面板(右侧上方):调整 3D 视图的视角(如第三人称、俯视图、跟随机器人),支持保存常用视角;
- Time 面板(右侧中间):控制时间同步(如实时显示 / 回放历史数据),新手默认用「Real Time」即可;
- 工具栏(顶部):快捷操作(视角调整、保存配置、重置视图),核心按钮要记牢。
3. 核心概念:Fixed Frame
- 定义:RViz2 所有可视化数据的「全局参考坐标系」,所有数据都会基于这个坐标系显示位置;
- 作用:若 Fixed Frame 配置错误,会导致「数据不显示」或「显示位置混乱」;
- 新手推荐配置:优先选
map(全局地图坐标系)或base_link(机器人基坐标系),根据场景切换; - 配置位置:Displays 面板顶部的「Fixed Frame」下拉框选择。
4. 数据展示逻辑
RViz2 本质是「话题订阅者」,所有可视化数据都来自 ROS2 话题,核心逻辑:
-
机器人节点发布数据到指定话题(如激光数据→
/scan、TF→/tf); -
RViz2 在 Displays 面板添加对应「显示类型」,并订阅目标话题;
-
RViz2 接收话题数据,转化为 3D 图形展示在主视图。
→ 核心结论:
要显示某类数据,必须满足「机器人发布对应话题 + RViz2 配置对应显示类型」
RViz2 常用功能实操
所有功能都围绕「Displays 面板添加显示项」展开,以下是新手最常用的 8 大功能,附详细配置步骤。
前提:基础准备
启动 RViz2 后,先做 2 个基础配置(避免后续踩坑):
- Displays 面板顶部「Fixed Frame」:选择
base_link(先以机器人基坐标为参考); - 工具栏点击「重置视角」(Reset View),让视图回到默认位置。
功能 1:显示 TF 坐标变换(必备!验证坐标系配置)
核心用途:查看所有坐标系的位置和父子关系,验证 TF 广播是否正确(如激光、相机相对于机器人的位置)
配置步骤
-
Displays 面板点击「Add」→ 左侧列表选择「TF」→ 点击「OK」;
-
启用 TF 显示项(默认勾选),主视图会显示各坐标系的「坐标轴」(X 红、Y 绿、Z 蓝);
-
关键配置(优化显示):
- 勾选「Show Names」:显示坐标系名称(如
base_link、laser); - 勾选「Show Axes」:显示坐标轴(默认勾选);
- 调整「Axis Length」:坐标轴长度(默认 1m,按需调大 / 小);
- 勾选「Show Names」:显示坐标系名称(如
-
验证:若 TF 广播正常,能看到各坐标系按实际安装位置排列(如
laser在base_link前方 0.2m 处)。
功能 2:显示机器人模型(URDF)
核心用途:可视化机器人的 3D 模型,验证 URDF 文件是否正确(无人机、机械臂、移动机器人都适用)
配置步骤
-
前提:机器人节点已发布 URDF 到
/robot_description话题(大部分机器人启动后会自动发布); -
Displays 面板「Add」→ 选择「RobotModel」→「OK」;
-
关键配置(必改):
- 「Robot Description Topic」:默认
/robot_description(无需改,和节点发布一致即可); - 「Visual Enabled」:勾选(显示机器人外观);
- 「Collision Enabled」:可选勾选(显示碰撞箱,调试避障用);
- 「Robot Description Topic」:默认
-
效果:主视图会显示机器人的 3D 模型,模型姿态会跟随 TF 实时变化。
功能 3:显示激光雷达数据(核心!激光调试必备)
核心用途:查看激光雷达的点云数据,验证激光是否正常、是否有障碍物
配置步骤(适配标准激光数据 sensor_msgs/msg/LaserScan)
-
Displays 面板「Add」→ 选择「LaserScan」→「OK」;
-
关键配置(必改!否则不显示):
- 「Topic」:选择激光话题(通常是
/scan,按实际节点发布的话题选择); - 「Fixed Frame」:若全局 Fixed Frame 不对,可单独设置为
laser(激光坐标系);
- 「Topic」:选择激光话题(通常是
-
优化显示:
- 「Size (m)」:调整激光点的大小(默认 0.05m,调大更清晰);
- 「Color」:选择激光点颜色(默认白色,可改红色);
-
验证:激光正常工作时,主视图会显示一圈点云,对应周围环境(有障碍物的地方有点,空旷处无点)。
功能 4:显示图像数据(相机调试必备)
核心用途:查看相机采集的图像,验证相机是否正常工作(支持单目 / 双目相机)
配置步骤(适配标准图像数据 sensor_msgs/msg/Image)
-
Displays 面板「Add」→ 选择「Image」→「OK」;
-
关键配置:
- 「Topic」:选择相机话题(通常是
/camera/image_raw,按实际选择); - 「Image Transport」:默认「raw」(无需改);
- 「Topic」:选择相机话题(通常是
-
效果:RViz2 会弹出独立窗口显示相机实时图像,也可在主视图侧边显示(调整「Position」参数)。
功能 5:显示路径(导航 / 无人机航迹必备)
核心用途:查看导航规划的路径或无人机的飞行航迹,验证路径规划是否合理
配置步骤(适配标准路径数据 nav_msgs/msg/Path)
-
Displays 面板「Add」→ 选择「Path」→「OK」;
-
关键配置:
- 「Topic」:选择路径话题(如导航路径
/plan、无人机航迹/flight_path); - 「Color」:设置路径颜色(推荐红色,醒目);
- 「Line Width」:调整路径线条粗细(默认 0.02m);
- 「Topic」:选择路径话题(如导航路径
-
效果:主视图会显示规划的路径线条,路径节点会实时更新。
功能 6:显示姿态(无人机 / 机器人姿态必备)
核心用途:查看机器人 / 无人机的姿态(俯仰、滚转、偏航),验证 IMU 数据或姿态解算是否正确
配置步骤(适配标准姿态数据 geometry_msgs/msg/PoseStamped)
-
Displays 面板「Add」→ 选择「Pose」→「OK」;
-
关键配置:
- 「Topic」:选择姿态话题(如
/drone/pose); - 「Shape」:选择姿态显示样式(箭头 / 轴 / 球体,推荐「Arrow」);
- 「Arrow Length」:调整箭头长度(默认 1m,适配无人机大小);
- 「Topic」:选择姿态话题(如
-
效果:箭头方向对应机器人 / 无人机的朝向,箭头姿态实时跟随话题数据变化。
功能 7:显示点云(3D 激光 / 深度相机必备)
核心用途:查看 3D 激光或深度相机的点云数据,构建环境三维模型
配置步骤(适配标准点云数据 sensor_msgs/msg/PointCloud2)
-
Displays 面板「Add」→ 选择「PointCloud2」→「OK」;
-
关键配置:
- 「Topic」:选择点云话题(如
/points_raw); - 「Fixed Frame」:设置为
lidar(3D 激光坐标系); - 「Color Transformer」:选择点云着色方式(「RGB8」按颜色着色,「Intensity」按强度着色);
- 「Topic」:选择点云话题(如
-
优化:调整「Size」让点云更清晰,勾选「Autocompute Intensity Bounds」自动适配强度范围。
功能 8:保存 / 加载配置
每次调试都要重新添加显示项?保存配置文件,下次直接加载,效率翻倍!
保存配置
- 配置完所有显示项后,点击顶部菜单栏「File」→「Save Config As」;
- 选择保存路径(建议存在功能包的
config/目录下),命名为xxx.rviz(如drone_debug.rviz);
加载配置
# 方式1:命令行加载
rviz2 -d ~/ros2_ws/src/你的包名/config/drone_debug.rviz
# 方式2:launch文件加载(推荐,和节点一起启动)
# 后续launch示例会包含
实战配置示例:无人机调试 RViz2 配置
以「无人机 + 激光雷达」为例,提供完整的 RViz2 配置逻辑,可直接复用:
1. 核心显示项配置清单
- Fixed Frame:
base_link(无人机基坐标系); - TF:显示无人机各坐标系(
base_link、laser、imu); - RobotModel:显示无人机 3D 模型(话题
/robot_description); - LaserScan:显示激光数据(话题
/scan); - Pose:显示无人机姿态(话题
/drone/pose); - 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
])
常见问题排查
-
数据不显示?→ 先查 3 点:
- Fixed Frame 是否配置正确(优先试
base_link); - 对应话题是否有数据(
ros2 topic echo 话题名验证); - Displays 面板中「Topic」是否选对,显示项是否勾选「Enabled」;
- Fixed Frame 是否配置正确(优先试
-
激光点云乱飘?→ 激光坐标系(
laser)的 TF 配置错误,重新校准安装位置; -
机器人模型不显示?→ 确认
/robot_description话题有数据,RobotModel 的「Topic」配置正确; -
视图看不清?→ 用工具栏调整视角(滚轮缩放、左键拖动平移、右键旋转),或重置视角。
总结
核心知识点
- RViz2 是「可视化调试工具」,核心是订阅 ROS2 话题并展示数据,不参与控制;
- Fixed Frame 是全局参考系,配置错误会导致数据不显示,新手优先用
base_link; - 所有可视化功能都需在「Displays 面板」添加对应显示项,并配置正确的话题。
新手必掌握的核心功能优先级
- TF 显示(验证坐标配置);
- 激光 / LaserScan 显示(传感器调试);
- 机器人模型 / RobotModel 显示(模型验证);
- 保存 / 加载配置(提升效率)。
关键操作
- 启动:
rviz2或rviz2 -d 配置文件.rviz; - 核心:Displays 面板「Add」添加显示项,配置「Topic」和「Fixed Frame」;
- 排错:先验证话题是否有数据,再检查 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/
常见问题排查
-
录制后 Bag 文件为空 / 无数据:
- 检查录制的话题是否有数据发布(
ros2 topic echo /话题名验证); - 避免录制不存在的话题(
ros2 topic list确认话题活跃); - 录制时不要过早按 Ctrl+C(至少等待 1 秒,确保数据写入)。
- 检查录制的话题是否有数据发布(
-
回放时节点收不到数据:
- 检查回放的话题名是否和节点订阅的一致(用
ros2 topic list确认); - 回放时添加
--remap修正话题名(如/scan改为/demo/scan); - 检查 Bag 文件是否包含该话题(
ros2 bag info验证)。
- 检查回放的话题名是否和节点订阅的一致(用
-
Bag 文件过大:
- 避免用
-a录制所有话题,按需指定话题名; - 录制时启用压缩(
--compression zstd); - 用
ros2 bag filter提取关键数据,删除冗余内容。
- 避免用
-
回放速度异常(过快 / 过慢):
- 用
--rate调整回放速度(如--rate 1.0恢复原速度); - 检查系统时间是否同步(Bag 依赖精确时间戳)。
- 用
总结
核心知识点
- ROS2 Bag 是话题数据的录制 / 回放工具,仅支持话题(不支持服务 / 动作),文件格式为 SQLite(.db3);
- 录制核心:按需指定话题(避免
-a)、用-o命名文件、启用压缩降低大小; - 回放核心:支持速度调整、话题重映射、时间偏移,可精准复现录制场景;
- 进阶操作:用
filter提取关键数据,减小文件体积。
高频命令
- 录制:
ros2 bag record -o <文件名> <话题1> <话题2>; - 查看信息:
ros2 bag info <Bag目录>; - 回放:
ros2 bag play <Bag目录>(常用参数:--rate/--topics/--loop); - 过滤:
ros2 bag filter <输入> <输出> <过滤表达式>。
关键使用场景
- 复现问题:录制故障场景的话题数据,离线回放调试;
- 算法调试:录制传感器数据(激光 / 相机),离线测试导航 / 感知算法;
- 数据分享:将 Bag 文件发给同事,无需现场即可复现场景。
11.urdf
URDF 核心认知
URDF 是基于 XML 的机器人描述格式,核心作用是将机器人拆解为 "连杆(Link)" 和 "关节(Joint)",定义:
- 📏 几何属性:每个连杆的形状(长方体 / 圆柱 / 球体)、尺寸、外观(颜色 / 材质);
- 📍 位置关系:关节连接的两个连杆的相对位置、旋转关系;
- ⚖️ 物理属性:连杆的质量、惯性矩阵,关节的运动范围(限位)、阻尼等;
- 🔌 传感器挂载:激光雷达、相机等传感器在连杆上的安装位置。
URDF 解决的核心问题
机器人是由多个部件组成的刚体系统,URDF 把 "物理机器人" 抽象为 "连杆 - 关节树",让 ROS2 能:
- 在 RViz2 中可视化机器人模型;
- 在 Gazebo 中进行物理仿真;
- 计算运动学 / 动力学(如逆运动学求解、碰撞检测);
- 定位传感器相对于机器人基坐标的位置(结合 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处。
常见问题排查
-
RViz2 中看不到机器人模型:
- 检查
Fixed Frame是否设置为 URDF 中的根连杆(如base_link); - 检查
robot_state_publisher是否正确加载 URDF(ros2 param get /robot_state_publisher robot_description验证); - 检查 URDF 语法(
check_urdf),确保无语法错误。
- 检查
-
check_urdf 报错 "Invalid XML":
- 检查 XML 标签是否闭合(如
<link>必须有</link>); - 检查属性值是否合法(如
xyz是 3 个数字,用空格分隔); - 避免中文 / 特殊字符(URDF 仅支持 ASCII 字符)。
- 检查 XML 标签是否闭合(如
-
Gazebo 仿真中机器人 "飘起来":
- 缺少
<inertial>节点(Gazebo 必需质量 / 惯性矩阵); - 惯性矩阵设置不合理(如质量为 0,或惯性值过大 / 过小)。
- 缺少
-
关节运动异常(如旋转超出限位):
- 检查
<joint>的<limit>节点(如lower/upper定义角度范围); - 关节类型错误(如需旋转用
revolute,而非fixed)。
- 检查
总结
核心知识点
- URDF 是 ROS2 描述机器人的 XML 格式,核心是
<link>(连杆)和<joint>(关节),定义机器人的几何 / 物理 / 位姿属性; - 关节类型中
fixed(固定)最常用(传感器安装),revolute(旋转)用于运动关节; - URDF 会自动生成 TF 变换树,通过
robot_state_publisher广播 TF 变换; - Xacro 是 URDF 的简化版,支持变量 / 宏,大幅降低编写复杂度(新手优先用 Xacro)。
高频命令
- 语法检查:
check_urdf <urdf文件>(编写后第一步); - 变换树生成:
urdf_to_graphiz <urdf文件>(可视化连杆 - 关节关系); - 可视化:
ros2 run robot_state_publisher robot_state_publisher --ros-args -p robot_description:="$(cat <urdf文件>)"+ RViz2; - Xacro 编译:
xacro <xacro文件> > <urdf文件>。
关键使用流程
- 编写 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.xacro、laser.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
常见问题排查
- 编译报错 "undefined variable/arg"
- 检查变量 / 参数名是否拼写错误(区分大小写,如
base_length≠Base_Length); - 确认变量 / 参数在使用前已定义(Xacro 按顺序解析,先定义后使用);
- 包含文件时,确认参数已传递(如
<xacro:arg>需在<xacro:include>中赋值)。
- 数学运算报错 "invalid expression"
- 确保运算表达式在
${}内(如${a + b},而非$a + b); - 避免除以 0、非法函数调用(如
deg2rad("90")字符串转角度,需用数值deg2rad(90)); - 复杂运算拆分(如
${(a + b) * c}加括号明确优先级)。
- RViz2 中模型显示异常
- 编译后的 URDF 语法错误(先用
check_urdf验证); - 宏参数传递错误(如车轮偏移量计算错误导致位置异常);
- 缺少
xmlns:xacro命名空间(根节点必须加xmlns:xacro="http://www.ros.org/wiki/xacro")。
- 条件判断不生效
- 布尔值需用
${true}/${false},而非true/false(如<xacro:if value="${has_laser}">); - 确认参数传递正确(编译时传参需用
:=,而非=)。
总结
核心知识点
- Xacro 是 URDF 的增强版,核心特性:变量(property)、宏(macro)、参数(arg)、数学运算、条件判断;
- Xacro 需编译为 URDF 才能使用,编译命令:
xacro xxx.xacro [参数名:=值] > xxx.urdf; - 模块化拆分是编写复杂模型的核心技巧,通过
<xacro:include>组合子文件; - 根节点必须添加 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}"> |
关键使用流程
- 拆分模块化 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. 快速复用步骤
- 复制所需模块的宏定义到你的 Xacro 文件;
- 修改宏调用时的参数(如尺寸、安装位置、质量);
- 编译为 URDF 验证:
xacro your_robot.xacro > your_robot.urdf; - 用
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>
总结
核心模板清单
- 底盘 + 车轮:移动机器人基础,适配差速小车;
- 激光雷达 + 相机 + IMU:常用传感器,固定关节安装;
- 机械臂关节 + 云台:适用于机械臂 / 云台类机器人;
- 脚轮:从动轮,适配三轮 / 四轮小车。
关键修改参数
| 模块 | 核心修改参数 | 示例值 |
|---|---|---|
| 底盘 | 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° |