ROS2 发布 /joint_states 的五种方式 — Gazebo 版本全解析目录

  1. [前言:为什么 /joint_states 如此重要](#前言:为什么 /joint_states 如此重要)
  2. [Gazebo 版本演进与 ROS2 兼容矩阵](#Gazebo 版本演进与 ROS2 兼容矩阵)
  3. [五种发布 /joint_states 的方式](#五种发布 /joint_states 的方式)
  4. [Gazebo 插件配置详解](#Gazebo 插件配置详解)
  5. [使用 rqt_plot 实时可视化](#使用 rqt_plot 实时可视化)
  6. 常见问题排查
  7. 总结与建议

前言:为什么 /joint_states 如此重要

在 ROS2 机器人系统中,/joint_states 是一个核心话题,它使用 sensor_msgs/msg/JointState 消息类型,承载着机器人所有关节的**位置(position)、速度(velocity)和力矩(effort)**信息。几乎所有上层应用都依赖它:

  • robot_state_publisher :订阅 /joint_states,计算并发布 TF 变换树
  • RViz2:通过 TF 树实现机器人模型的可视化运动
  • 运动规划(MoveIt2):需要实时关节状态来做碰撞检测和轨迹跟踪
  • rqt_plot:实时绘制关节曲线,用于调试和分析

然而,/joint_states 不会凭空出现------必须有节点主动发布。本文将系统梳理在不同场景下(有无 ros2_control、不同 Gazebo 版本)如何正确发布这个话题。


Gazebo 版本演进与 ROS2 兼容矩阵

Gazebo 仿真器经历了从 Gazebo Classic (数字编号版本)到 Gazebo(字母编号版本,原名 Ignition)的重大架构变革。理解这个演进是正确配置插件的前提。

Gazebo Classic vs 新版 Gazebo

特性 Gazebo Classic(旧版) Gazebo(新版)
版本号 Gazebo 9 / 11 Fortress / Harmonic / Ionic 等
渲染引擎 OGRE(较旧) OGRE 2.x(支持光线追踪)
ROS 桥接 gazebo_ros_pkgs ros_gz
插件体系 SDF + 自定义 C++ 插件 SDFormat + 模块化架构
状态 已停止新功能开发 1 活跃开发,推荐新项目使用 2

ROS2 与 Gazebo 版本兼容矩阵

下表列出了当前受支持的 ROS2 与 Gazebo 组合 2

ROS2 版本 Gz Fortress Gz Garden Gz Harmonic Gz Ionic Gazebo Classic 11
Foxy (LTS) - - - - Citadel
Humble (LTS) 推荐 可用 可用 - Classic 11
Iron 推荐 可用 可用 - Classic 11
Jazzy (LTS) - - 推荐 可用 不支持
Rolling - 可用 可用 推荐 不支持

注意 :Gazebo Classic 未发布到 Ubuntu Noble(24.04),因此 gazebo_ros2_control 从未发布到 Jazzy 和 Rolling 1。如果你使用 Jazzy 或更新版本,必须使用新版 Gazebo(Harmonic/Ionic)

查看你的 Gazebo 版本

bash 复制代码
# Gazebo Classic
gazebo --version

# 新版 Gazebo
gz sim --versions

# 通过 apt 查看
dpkg -l | grep gazebo
dpkg -l | grep gz-sim

比如,我这里就是Ubuntu22.04+ ROS2 Humble + Gazebo11

bash 复制代码
wu@wu:~$ gazebo --version
Gazebo multi-robot simulator, version 11.10.2
Copyright (C) 2012 Open Source Robotics Foundation.
Released under the Apache 2 License.
http://gazebosim.org

五种发布 /joint_states 的方式

无论你使用哪种 Gazebo 版本,发布 /joint_states 的核心思路一致。下面按复杂度从低到高逐一介绍。

方式一:joint_state_publisher(静态值)

最简单的方式,直接读取 URDF 中定义的关节,发布固定初始值。

bash 复制代码
ros2 run joint_state_publisher joint_state_publisher

特点:关节值固定不变,适合仅验证 URDF 模型和 RViz2 显示。

方式二:joint_state_publisher_gui(手动滑块)

带 Qt 界面,可以用滑块手动拖动各关节角度,实时发布到 /joint_states

bash 复制代码
ros2 run joint_state_publisher_gui joint_state_publisher_gui

特点 :无需真实硬件或仿真,适合调试和快速验证。需要安装 ros-<distro>-joint-state-publisher-gui

方式三:Gazebo 插件(仿真场景,不用 ros2_control)

这是本文的重点。通过在 URDF 中嵌入 Gazebo 插件,让仿真引擎自动读取关节物理状态并发布。

核心优势 :不需要 ros2_control 框架,配置简单,适合轻量级仿真项目。

配置方式因 Gazebo 版本而异,详见下一节。

方式四:ros2_control(工业级方案)

通过 gazebo_ros2_control 插件 + joint_state_broadcaster 控制器实现 3

bash 复制代码
# 查看已加载的控制器
ros2 control list_controllers

# 加载并激活 joint_state_broadcaster
ros2 control load_controller joint_state_broadcaster
ros2 control set_controller_state joint_state_broadcaster active

特点:功能最强大,支持轨迹跟踪、力矩控制等,但配置复杂,适合正式项目。

方式五:自定义节点(完全灵活)

自己写 Python 或 C++ 节点,手动构造 sensor_msgs/msg/JointState 消息并发布。

python 复制代码
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import JointState

class MyJointStatePublisher(Node):
    def __init__(self):
        super().__init__('my_joint_state_publisher')
        self.pub = self.create_publisher(JointState, '/joint_states', 10)
        self.timer = self.create_timer(0.02, self.publish)  # 50Hz
        self.names = ['joint1', 'joint2', 'joint3',
                      'joint4', 'joint5', 'joint6']

    def publish(self):
        msg = JointState()
        msg.header.stamp = self.get_clock().now().to_msg()
        msg.name = self.names
        msg.position = [0.0] * 6   # 填入实际关节角度
        msg.velocity = [0.0] * 6
        msg.effort   = [0.0] * 6
        self.pub.publish(msg)

特点:最灵活,适合自研驱动、自定义算法等场景。

五种方式对比

方式 难度 数据来源 需要 Gazebo 需要 ros2_control 适用场景
joint_state_publisher URDF 静态值 验证 URDF / RViz
joint_state_publisher_gui 手动滑块 调试、无硬件测试
Gazebo 插件 仿真物理引擎 轻量级仿真
ros2_control 仿真/硬件 正式项目
自定义节点 自定义 自研驱动

Gazebo 插件配置详解

在不使用 ros2_control 的前提下,通过 Gazebo 插件发布 /joint_states 是最常用的仿真方案。不同 Gazebo 版本的插件配置写法有显著差异。

Gazebo Classic + ROS2(推荐 Humble/Iron 用户)

使用 libgazebo_ros_joint_state_publisher.so 插件,在 URDF 的 <robot> 标签内添加:

xml 复制代码
<!-- 关节状态发布插件 -->
<gazebo>
    <plugin name="joint_state_publisher"
            filename="libgazebo_ros_joint_state_publisher.so">
        <ros>
            <namespace>/</namespace>
            <remapping>~/out:=/joint_states</remapping>
        </ros>
        <update_rate>30</update_rate>
        <joint_name>front_left_steering_joint</joint_name>
        <joint_name>front_right_steering_joint</joint_name>
        <joint_name>front_left_wheel_joint</joint_name>
        <joint_name>front_right_wheel_joint</joint_name>
        <joint_name>rear_left_wheel_joint</joint_name>
        <joint_name>rear_right_wheel_joint</joint_name>
    </plugin>
</gazebo>

关键点

  • <ros> 标签是 ROS2 版 gazebo_ros_pkgs 的标准格式
  • <remapping>~/out:=/joint_states</remapping> 将插件默认输出话题映射到 /joint_states
  • <update_rate> 设置发布频率(Hz),建议 30-50Hz
  • 每个需要监控的关节都要通过 <joint_name> 声明

Gazebo Classic + ROS1

ROS1 版本写法完全不同,没有 <ros> 标签:

xml 复制代码
<gazebo>
    <plugin name="joint_state_publisher"
            filename="libgazebo_ros_joint_state_publisher.so">
        <topicName>joint_states</topicName>
        <update_rate>30</update_rate>
        <joint_name>joint1</joint_name>
        <joint_name>joint2</joint_name>
    </plugin>
</gazebo>

注意 ROS1 直接用 <topicName> 指定话题名,不需要 <remapping>

新版 Gazebo(Fortress/Harmonic/Ionic)+ ROS2

新版 Gazebo 使用 ros_gz 包替代 gazebo_ros_pkgs 4。关节状态发布通过 gz_ros2_controlros_gz 桥接实现:

xml 复制代码
<!-- 新版 Gazebo 使用 SDFormat + ros_gz 桥接 -->
<!-- 方式 A:通过 ros_gz 桥接(轻量级) -->
<gazebo>
    <plugin
        filename="gz-sim-joint-state-publisher-system"
        name="gz::sim::systems::JointStatePublisher">
    </plugin>
</gazebo>

<!-- 在 launch 文件中创建桥接 -->
<!-- ros2 run ros_gz bridge /model/{model_name}/joint_states@sensor_msgs/msg/JointState[ignition.msgs.JointState -->

注意 :新版 Gazebo 的插件体系完全不同。如果从 Gazebo Classic 迁移,需要参考官方迁移指南 4,URDF 中的 <gazebo> 插件配置需要重写。

版本配置速查表

环境 插件文件名 配置标签风格 话题映射方式
Classic + ROS2 libgazebo_ros_joint_state_publisher.so <ros><remapping> <remapping>~/out:=/joint_states</remapping>
Classic + ROS1 libgazebo_ros_joint_state_publisher.so <topicName> <topicName>joint_states</topicName>
新版 Gazebo + ROS2 gz-sim-joint-state-publisher-system SDFormat + ros_gz 桥接 launch 文件中 ros_gz bridge

使用 rqt_plot 实时可视化

/joint_states 成功发布后,可以用 rqt_plot 实时绘制关节数据曲线。

安装

bash 复制代码
# 安装 rqt_plot 及相关工具
sudo apt install ros-<distro>-rqt ros-<distro>-rqt-common-plugins ros-<distro>-rqt-plot

# 推荐安装更快的绘图后端
pip install pyqtgraph --break-system-packages

命令行启动

由于 positionvelocityeffort 是数组,必须指定索引

bash 复制代码
# 绘制第 0 个关节的位置
ros2 run rqt_plot rqt_plot /joint_states/position[0]

# 同时绘制多个关节
ros2 run rqt_plot rqt_plot /joint_states/position[0] /joint_states/position[1] /joint_states/position[2]

# 绘制速度
ros2 run rqt_plot rqt_plot /joint_states/velocity[0] /joint_states/velocity[1]

GUI 交互式添加

bash 复制代码
ros2 run rqt_plot rqt_plot

在界面顶部的 "Topic" 输入框中输入完整字段路径(如 /joint_states/position[0]),点击 + 按钮添加。输入框支持自动补全。

绘图后端选择

点击标题栏齿轮图标可切换后端:

后端 性能 安装方式
pyqtgraph 最快(推荐) pip install pyqtgraph
matplotlib 较慢 系统自带
qwt plot python3-qwt

常见问题排查

ros2 topic echo /joint_states 没有输出

这是最常见的问题,按以下流程排查:

bash 复制代码
# 1. 确认话题是否存在
ros2 topic list | grep joint_states

# 2. 确认是否有发布者
ros2 topic info /joint_states

# 3. 查看发布频率
ros2 topic hz /joint_states

# 4. 查看消息内容
ros2 topic echo /joint_states

排查思路

  • 话题不存在 → 没有节点发布,需要启动 joint_state_publisher 或加载控制器
  • 有 Subscribers 但无 Publishers → 同上,需要启动发布节点
  • 话题存在且有 Publishers 但无数据 → 检查 Gazebo 插件配置是否正确

rqt_plot 曲线不显示

  • 数组未指定索引/joint_states/position 是数组,必须写成 /joint_states/position[0]
  • 字段路径不完整:必须写到具体数值字段的完整路径
  • 从 bag 回放 :需要使用仿真时间,加 --clock 参数

Gazebo 插件加载失败

  • 确认插件文件存在:rospack find gazebo_ros 或检查 lib 目录
  • 确认 <joint_name> 与 URDF 中定义的关节名完全一致(区分大小写)
  • 查看 Gazebo 终端输出的错误日志

总结与建议

如何选择方案

你的场景 推荐方案
快速验证 URDF / RViz 显示 joint_state_publisher
手动调试关节运动 joint_state_publisher_gui
Gazebo Classic 仿真,轻量级项目 libgazebo_ros_joint_state_publisher.so 插件
Gazebo Classic 仿真,正式项目 ros2_control + joint_state_broadcaster
新版 Gazebo(Harmonic/Ionic) ros_gz 桥接 或 gz_ros2_control
自研硬件驱动 自定义 Python/C++ 节点

最后提醒 :无论选择哪种方案,核心都是确保 /joint_states 话题上有数据在发布。养成 ros2 topic echo /joint_statesros2 topic hz /joint_states 的排查习惯,可以快速定位 90% 的问题。


参考资料

  1. Open Robotics, gazebo_ros2_control --- 官方仓库文档。说明 Gazebo Classic 不再支持 Jazzy/Rolling。https://index.ros.org/r/gazebo_ros2_control/
  2. Gazebo 官方文档, Installing Gazebo with ROS --- ROS2 与 Gazebo 版本兼容矩阵。https://gazebosim.org/docs/ionic/ros_installation/
  3. ros2_control 官方文档, joint_state_broadcaster --- 通过控制器发布 /joint_states。https://control.ros.org/humble/doc/gazebo_ros2_control/doc/index.html
  4. Gazebo 官方文档, Migrating ROS 2 packages that use Gazebo Classic --- 从 Classic 迁移到新版 Gazebo 的指南。https://gazebosim.org/docs/latest/migrating_gazebo_classic_ros2_packages/
  5. ROS Wiki, rqt_plot --- rqt_plot 使用文档与语法说明。http://ftp.osuosl.org/.1/ros/ros_wiki_mirror/rqt_plot.html