
兄弟们如果你和我一样是在 Ubuntu 22.04 上装的 ROS2 Humble又不想守着老掉牙的 Gazebo Classic而是想直接上 Gazebo Harmonic 做 ros2_control 仿真那 gz_ros2_control 这个插件十有八九要把你折磨一遍。我前后折腾了两天踩遍了“插件加载失败”“controller_manager 起不来”“编译时找不到 gz-sim 版本”这些坑才把环境彻底跑通。这篇东西就是把我当时的完整排查过程和最终配置方案整理出来给正要走这条路的人当个参考省得在同一个坑里反复横跳。这篇文章适合两类人一类是 ROS2 Humble 已经装好、想把新版 Gazebo 和 ros2_control 打通做机器人控制仿真的人另一类是已经在跑 gz_ros2_control 但遇到各种诡异报错、决定静下心从头理一遍环境的人。我会尽量讲清楚每一步背后的原因不搞那种“照着敲就行”的玄学教程这样你后面遇到别的幺蛾子也能自己排查。1. 环境准备Ubuntu 22.04 下的 ROS2 Humble 与 Gazebo Harmonic 安装1.1 ROS2 Humble 安装要点很多教程会让你从配置 locale 开始实际上对于大部分国内开发者来说更关键的是软件源和 keys 这一关。Ubuntu 22.04 对应 ROS2 Humble官方推荐的安装方式就是先添加 ROS2 源的 key再把源写进系统。sudo apt update sudo apt install curl gnupg lsb-release sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo deb [arch$(dpkg --print-architecture) signed-by/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release echo $UBUNTU_CODENAME) main | sudo tee /etc/apt/sources.list.d/ros2.list /dev/null sudo apt update装完源之后桌面版直接装ros-humble-desktop就行这里面带了 rviz、demo 等一堆常用工具仿真调试基本够用。如果你打算做纯机器人控制仿真也可以只装ros-humble-ros-base加上后续的 ros2_control 相关包更轻量。sudo apt install ros-humble-desktop python3-colcon-common-extensions python3-rosdep echo source /opt/ros/humble/setup.bash ~/.bashrc source ~/.bashrc这里有个新手特别容易漏的步骤python3-colcon-common-extensions不装的话后面源码编译 gz_ros2_control 时colcon命令会缺很多东西我一开始就是没装结果编译到一半各种 python 模块找不到白白浪费了半小时。另外 rosdep 建议顺手初始化一下后面源码编译时经常要用sudo rosdep init rosdep update如果 rosdep init 报错说系统里已经存在配置文件说明你可能装过别的 ROS 版本直接跳过 init 只做 update 就行不影响使用。1.2 安装 Gazebo HarmonicGazebo Harmonic 对应的软件包是gz-harmonic底层是 gz-sim8。它和 Gazebo Classicgazebo11是两套完全不同的东西命令也不一样Classic 用gazebo启动Harmonic 用gz sim启动。这两个包可以在系统里共存不影响彼此但要注意别配混了。安装 Harmonic 需要添加 OSRF 的软件源sudo apt-get install curl lsb-release gnupg sudo curl https://packages.osrfoundation.org/gazebo.gpg --output /usr/share/keyrings/pkgs-osrf-archive-keyring.gpg echo deb [arch$(dpkg --print-architecture) signed-by/usr/share/keyrings/pkgs-osrf-archive-keyring.gpg] http://packages.osrfoundation.org/gazebo/ubuntu-stable $(lsb_release -cs) main | sudo tee /etc/apt/sources.list.d/gazebo-stable.list /dev/null sudo apt-get update sudo apt-get install gz-harmonic装完验证一下gz sim --version能输出来类似Gazebo Simulator version 8.x.x这样的信息就说明装成功了。注意如果你之前装过 Gazebo Fortress 或者 Garden系统里会有多套 gz-sim 版本共存后面编译 gz_ros2_control 时极容易因为 CMake 找到了错误的版本而踩坑我在第 3 节会专门讲。1.3 ros_gz 桥接包与 gz_ros2_control 的三种安装方式先说 ros_gz 这个包。它负责 ROS2 和 Gazebo 之间的 topic、service 桥接以及一些常用 launch 工具。在 Humble 下直接用 apt 装ros-humble-ros-gz是给 Gazebo Fortressgz-sim6准备的如果你要用 Harmonic这个 apt 版本大概率桥接不上需要用源码编译新版 ros_gz。gz_ros2_control 就更麻烦了。它是 ros-controls 官方仓库里的项目作用是让 ros2_control 的 Controller Manager 能控制新式 Gazebo 里的关节。目前有三种安装方式apt 直接装ros-humble-gz-ros2-control省事但默认依赖的 Gazebo 版本和你系统里的 Harmonic 很可能对不上实际跑起来很容易出问题我第一轮就是这么翻车的。源码编译 ros-controls/gz_ros2_control 仓库指定正确分支灵活可控这是我自己最终采用的方式也是本文下面要详细讲解的方式。用 Docker 镜像如果你不想折腾宿主机环境可以找一个配好 Humble Harmonic 的镜像但这样调试时文件挂载、GPU 显示这些也要花时间处理不如本地环境直接。我建议有耐心的话直接走源码编译这条路线虽然前期麻烦一点但后面遇到问题你至少知道去哪看、怎么改。2. 理清 ros2_control 与 gz_ros2_control 的配合逻辑2.1 ros2_control 的四大件ros2_control 本身不是一个仿真工具它是一个硬件抽象层框架。官方概念里有四个核心角色Controller Manager、Controller控制器、Hardware Interface硬件接口、Robot Hardware机器人硬件也就是实际执行机构或仿真器。控制链路大概是这样的Controller Manager 统一管理所有 Controller比如关节轨迹控制器、状态广播器Controller 通过 command interface 发指令、通过 state interface 读状态Hardware Interface 负责把这些指令和状态翻译成真实硬件或仿真器能理解的格式真正的读写硬件动作则由 Hardware Interface 背后的实现去完成。这个设计的核心价值在于解耦。你的控制器逻辑可以完全不用关心底层到底是真实电机还是仿真关节只要遵循同样的接口规范就能在不同平台上无缝切换。这也是为什么仿真环境对机器人开发这么重要——先用仿真把控制逻辑调通再迁移到实物上代价小得多。2.2 gz_ros2_control 在整个链路里的位置gz_ros2_control 充当的就是 Robot Hardware 这一层它向 ros2_control 提供一个名为GazeboSimSystem的硬件接口实现。这个类继承了 ros2_control 的标准 Hardware Interface 接口并把关节读写操作映射到 Gazebo Sim 里的 joint 对象上。在 Gazebo Sim 里它是作为一个 system plugin 被加载到模型中的。插件启动时会帮你把/controller_manager节点拉起来同时读取配置好的 controllers.yaml把控制器配置交给 Controller Manager。仿真里每个关节的状态由 gz_ros2_control 周期性地从 Gazebo 中读取并发布控制器计算出来的指令再由 gz_ros2_control 写回 Gazebo 里的关节。所以你看到的现象往往是一旦插件加载成功ROS2 环境里会自动多出/controller_manager这个节点ros2 control list_controllers也能看到对应的控制器。如果这个节点没出现那问题基本都出在插件加载这一环。2.3 版本兼容矩阵哪些组合能跑、哪些必然踩坑版本匹配是 gz_ros2_control 最反直觉的地方。我一开始以为只要 ROS2 和 Gazebo 装好了就能用实际上不同分支的 gz_ros2_control 和不同 gz-sim 版本之间的匹配关系很讲究。ROS2 版本Gazebo 版本gz_ros2_control 来源稳定性HumbleGazebo Classic 11gazebo_ros2_controlapt很稳定教程最多HumbleFortressgz-sim6ros-humble-gz-ros2-controlapt比较稳定HumbleHarmonicgz-sim8源码编译GZ_SIM_VER8需要踩坑本文核心RollingHarmonicgz-sim8源码编译 main 分支推荐新项目使用这里要特别说明一下Humble 官方发布时主要适配的是 Gazebo Fortress所以 apt 仓库里的 ros-gz 和 gz_ros2_control 二进制包大多绑定 gz-sim6。如果你想在 Humble 上用 Harmonic就绕不开源码编译这条路而且编译时一定要让 CMake 找到 gz-sim8而不是默认的 gz-sim7 或 gz-sim6。这个“版本指向错误”的问题就是绝大多数插件兼容性报错的根源。3. gz_ros2_control 插件兼容性问题深度排查3.1 错误一Failed to load plugin libgz_ros2_control.so这是最经典也最让人崩溃的报错形式通常是 Gazebo 启动后在日志里打出一行[Err] [Plugin.hh:126] Failed to load plugin libgz_ros2_control.so: ... cannot open shared object file或者[Err] [SystemManager.cc:183] Failed to load system plugin [gz_ros2_control::GazeboSimSystem]看到这个先别慌。首要是确认系统里到底有没有这个 .so 文件find / -name libgz_ros2_control.so 2/dev/null如果找不到说明你源码编译后忘了sourceinstall 目录或者压根没编译成功。如果找到了但路径很怪比如在/workspace/install/...下面那就需要检查 Gazebo 有没有把那个目录加进插件搜索路径。可以手动把插件目录添加到环境变量里再启动export GZ_SIM_SYSTEM_PLUGIN_PATH/opt/ros/humble/lib:$HOME/gz_ros2_control_ws/install/lib如果你的 lib 是装在/opt/ros/humble/lib下的GZ_SIM_SYSTEM_PLUGIN_PATH一定要带这个路径。Gazebo Sim 搜索插件时会优先看这个变量不设置的话它大概率只找默认安装目录源码编译产物就找不到了。如果.so文件确实存在仍然加载失败那就要用ldd看它依赖的库是否齐全ldd /path/to/libgz_ros2_control.so | grep not found只要有依赖缺失基本可以断定是 gazebo 版本不对或者 ros2 环境没 source 完整补装对应版本的 gz-sim 开发库就行。我在实际调试中还遇到过一种情况libgz_ros2_control.so是编译好了但它在运行时依赖的libgz-sim8.so和我系统里装的libgz-sim7.so混在一起Gazebo 进程加载的时候直接冲突崩溃。这种就需要把旧版本 gz-sim7 的插件路径从GZ_SIM_SYSTEM_PLUGIN_PATH里彻底清掉别让两个版本同时出现在搜索路径里。3.2 错误二编译时找不到 gz-sim7 或 GZ_SIM_VER 指向错误源码编译 gz_ros2_control 的时候CMake 会尝试find_package(gz-sim${GZ_SIM_VER})。如果你直接按 README 默认命令编译它可能去找 gz-sim7 或者你系统里已有的其他版本。而你明明装的是 gz-sim8Harmonic 对应版本结果就是CMake Error at CMakeLists.txt:...: Could not find a package configuration file provided by gz-sim7解决方法有两种。第一种是编译时通过 CMake 参数显式指定版本cd ~/gz_ros2_control_ws colcon build --symlink-install --cmake-args -DGZ_SIM_VER8 -DCMAKE_BUILD_TYPERelease如果-DGZ_SIM_VER这个变量在你的分支里不被识别那就只能去改 CMakeLists.txt。找到文件里的 GZ_SIM_VER 相关行直接改成 8 再编译# 在 gz_ros2_control/CMakeLists.txt 里找到类似这样的行 # set(GZ_SIM_VER 7 CACHE STRING Gazebo Sim version) # 改成 set(GZ_SIM_VER 8 CACHE STRING Gazebo Sim version)我这里要提醒一句不同分支的变量名可能有区别有的是GZ_SIM_VER有的是USE_GZ_SIM_VERSION你搜一下 CMakeLists 里的 gz-sim 相关关键词就能看到。还有个小技巧在colcon build之前先跑一次cmake .. -LA看看有哪些缓存变量能少走很多弯路。编译成功后检查一下生成的动态库链接的是不是 gz-sim8ldd install/lib/libgz_ros2_control.so | grep gz-sim如果输出里同时出现 gz-sim7 和 gz-sim8那说明你的 CMake 路径里混了多个版本建议清理 build 目录重新配置只把 gz-sim8 的路径留在 CMAKE_PREFIX_PATH 里。3.3 错误三/controller_manager 节点不启动插件加载没报错Gazebo 也正常起来了但你打开另一个终端输入ros2 node list死活看不到/controller_manager。这种情况通常不是插件本身挂了而是插件的 ROS2 上下文初始化出了问题。先确认一下你的 SDF 文件里 plugin 标签到底有没有生效。可以在 Gazebo 启动日志里搜索gz_ros2_control或者GazeboSimSystem相关输出。如果插件确实被加载了但节点没起来常见原因有三个第一没有 source ROS2 环境。Gazebo 是在哪个终端启动的那个终端的 bashrc 里必须已经有/opt/ros/humble/setup.bash的 source否则插件里的 ROS2 初始化会静默失败。第二插件写错了作用域。gz_ros2_control的插件一般要放在model标签内部如果你把plugin放在了world下面它虽然也能被加载但可能找不到对应的模型关节controller_manager 建不起来。这一点特别坑因为我见过不少教程把 world 级和 model 级混着写导致最终行为不一致。第三controllers.yaml 的路径没解析对。在 SDF 里写$(find xxx)时如果插件内部没有做包路径解析就会拿不到文件。稳妥起见你先用绝对路径测试plugin filenamelibgz_ros2_control.so namegz_ros2_control::GazeboSimSystem parameters/home/yourname/catkin_ws/src/.../config/cartpole_controller.yaml/parameters /plugin等确认全链路跑通了再换成$(find ...)的写法也不迟。这种“先用绝对路径排除变量干扰”的思路其实适用于所有类似情况定位问题的时候能少一半工作量。3.4 错误四控制器列表为空JointStateBroadcaster 加载失败/controller_manager节点起来了但执行ros2 control list_controllers输出显示command not found或者控制器列表是空的。如果是command not found说明你缺ros2_control相关的命令工具装一下sudo apt install ros-humble-ros2-control ros-humble-ros2-controllers列表为空但节点存在就要检查 controllers.yaml 是否被正确加载。可以用以下命令看看 controller_manager 的参数里到底有没有 controller 配置ros2 param dump /controller_manager如果 yaml 没被加载你会看到参数前缀下面根本没有joint_state_broadcaster或joint_trajectory_controller这些字段。此时回到 SDF 里的parameters标签检查路径和文件名尤其注意 yaml 的格式必须符合 ros2_control 规范。还有个常见坑是use_sim_time。如果你的 controller_manager 设置了use_sim_time: true但 Gazebo 的时间同步没正常建立controller 会一直等待仿真时间推进表现就是加载成功了但状态一直是 inactive。JointStateBroadcaster 加载失败还有一种概率很低但很烦的原因controller 类型名写错了。ros2_control 里 broadcast 的类型是joint_state_broadcaster/JointStateBroadcaster你如果漏了前面的命名空间Controller Manager 会直接拒绝加载。报错信息会提示Class not found千万别只盯着插件层面排查。4. 手把手配置一份可用的 SDF 插件与控制文件4.1 SDF 文件里的 gz_ros2_control 插件怎么贴我以最经典的 cartpole 示例来说明。一个能跑通的最小 SDF 文件核心结构大概是这样sdf version1.9 world namedefault include urimodel://cartpole/uri namecartpole/name /include model namecartpole plugin filenamelibgz_ros2_control.so namegz_ros2_control::GazeboSimSystem parameters/your/absolute/path/to/cartpole_controller.yaml/parameters /plugin /model /world /sdf看到没有插件是挂在model下面不是world下面。有些示例会在 world 级别写相同的插件但根据我自己的测试在 model 级别写才是最不容易出错的。因为GazeboSimSystem需要绑定到具体的模型实例上才知道要控制哪些关节。如果你是用 URDF xacro 做机器人描述然后通过 ros_gz 的create功能生成 SDF那 URDF 里的gazebo_ros2_control扩展标签会被转换成 SDF 的plugin。这种情况下你其实可以只改 URDF 里的插件参数再由工具自动生成 SDF省去手动维护两个文件的烦恼。不过如果你是手工写 SDF这里有个容易被忽略的细节plugin标签里除了parameters有时候还需要写上ros子标签来指定命名空间和节点名。比如plugin filenamelibgz_ros2_control.so namegz_ros2_control::GazeboSimSystem ros remapping/controller_manager:/my_robot/controller_manager/remapping /ros parameters/your/path/controllers.yaml/parameters /plugin如果你只有一个机器人不写ros也没关系默认命名空间即可。但如果同一进程里同时有多个机器人模型就必须给每个插件的 controller_manager 设定不同命名空间否则它们会抢同一个节点名。这个坑在跑多机仿真时特别容易爆。4.2 controllers.yaml 怎么写才能被识别ros2_control 的 yaml 文件其实有非常固定的结构少了任何一层嵌套都可能导致配置加载失败。一个最简可用的例子controller_manager: ros__parameters: update_rate: 100 use_sim_time: true joint_state_broadcaster: type: joint_state_broadcaster/JointStateBroadcaster cartpole_controller: type: joint_trajectory_controller/JointTrajectoryController cartpole_controller: ros__parameters: joints: - slide_to_joint command_interfaces: - position state_interfaces: - position state_publish_rate: 100.0这里有个关键点controller_manager段落和cartpole_controller段落是平级的它们分别对应 controller_manager 节点的参数和具体控制器的参数。很多人把cartpole_controller写进了controller_manager下面结果控制器加载后完全读不到joints配置报一些莫名其妙的 “joint mismatch” 错误。关节名slide_to_joint要和你 SDF 模型里的关节名完全一致大小写敏感。command_interfaces和state_interfaces则决定了这个控制器是发位置指令还是速度、力矩指令。如果你模型里的关节是 revolute 的接口类型也可以是 effort这块要按自己模型的实际关节类型来设计。我建议的第一个测试配置不要搞太复杂先只跑一个joint_state_broadcaster确认关节状态能发出来再往上加控制器。这样能帮你把“插件问题”和“控制器配置问题”分开排查不然所有问题混在一起你会疯的。4.3 完整启动流程与 ros2 control 验证命令环境配好之后整个启动顺序我推荐这样来先在终端 A 里编译并 source 工作空间cd ~/gz_ros2_control_ws colcon build --symlink-install --cmake-args -DGZ_SIM_VER8 -DCMAKE_BUILD_TYPERelease source install/setup.bash如果编译报错说你缺少 gz-ros2-control 的依赖可以先安装sudo apt install ros-humble-ros2-control ros-humble-ros2-controllers ros-humble-hardware-interface终端 A 继续启动 Gazebogz sim -s -r your_robot.sdf-s表示 headless 模式-r表示开始运行如果你要可视化界面把-s去掉即可。打开终端 Bsource 同样的环境后先确认插件节点起来了ros2 node list正常情况下你会看到/gz_ros2_control或者/controller_manager这样的节点。如果看到多个类似名字的节点说明插件被加载了多次基本都是 SDF 里 plugin 写重了。然后查看控制器状态ros2 control list_controllers如果新加载的 controller 显示unconfigured手动加载并激活ros2 control load_controller joint_state_broadcaster ros2 control set_controller_state joint_state_broadcaster active激活之后在终端 C 里订阅关节状态ros2 topic echo /joint_states能刷出来数据说明 gz_ros2_control 和 ros2_control 整条链路已经打通了。之后再逐个加载你真正要用的轨迹控制器。这里我强烈安利一个调试命令ros2 control list_hardware_interfaces它会直接列出当前系统里所有可用的 command 和 state 接口。看到slide_to_joint/position [claimed]就说明插件已经正确把关节接口暴露给了 controller比瞎猜有效得多。5. 踩坑实录与快速排查速查表5.1 速查表错误现象 根因 处理方法我把这轮调试中遇到的高频问题整理成了一张表以后遇到直接从表里找答案就行。错误现象根本原因处理方法Failed to load plugin libgz_ros2_control.so插件路径没被 Gazebo 找到设置 GZ_SIM_SYSTEM_PLUGIN_PATH指向 install/lib 或 /opt/ros/humble/libPlugin loaded but no /controller_managerROS2 环境未 source或插件写在 world 级别在启动终端的 bashrc 里 source ROS2把 plugin 挪到 model 级别CMake error: Could not find gz-sim7CMake 默认找错版本编译时加 -DGZ_SIM_VER8或改 CMakeLists 里的 GZ_SIM_VERros2: command not found缺少 ros2_control 命令行工具安装 ros-humble-ros2-control 和 ros-humble-ros2-controllersController list emptycontrollers.yaml 没被加载检查 SDF 里 parameters 路径用绝对路径测试Controller loads but stuck inactiveuse_sim_time 不同步或类型名写错确认 use_sim_time: true检查 controller type 是否带命名空间join t_states 无数据joint 名不匹配或接口类型不正确用 ros2 control list_hardware_interfaces 核对关节接口运行中 crash依赖 gz-sim7 和 gz-sim8 混合系统里多版本 gz-sim 共存清理 GZ_SIM_SYSTEM_PLUGIN_PATH卸载旧版 gz-sim5.2 几条独家经验第一不要迷信 apt 版本的 gz_ros2_control。Humble Harmonic 这个组合天生就不是“apt install 一把梭”能搞定的组合老老实实源码编译并且把-DGZ_SIM_VER8写进自己的编译脚本里避免每次手动敲。第二善用GZ_SIM_RESOURCE_PATH。如果你的自定义模型不在默认资源路径下Gazebo 会提示找不到模型。设置这个变量指向你的模型目录能省去绝对路径带来的各种迷之 bugexport GZ_SIM_RESOURCE_PATH$HOME/ros2_ws/src/my_robot/models:$HOME/.gazebo/models export GZ_SIM_SYSTEM_PLUGIN_PATH$HOME/gz_ros2_control_ws/install/lib:/opt/ros/humble/lib第三善用ros2 param dump /controller_manager。这个命令能看到 controller_manager 实际加载的所有参数比任何日志都直观。如果joint_state_broadcaster的 type 参数不存在说明 yaml 压根没读进来别再去改代码了回去检查路径。第四如果你在同一个模型里放了多个执行器比如差速轮加机械臂记得每个执行器对应的 joint 要在 controllers.yaml 里正确分组。一个joint_trajectory_controller默认只会管理它joints列表里定义的关节没写进去的关节不会动这是初学者最容易忽略的“不是 bug 的 bug”。第五关于话题频率。如果joint_states刷新的频率和你设定的update_rate对不上先看看有没有开use_sim_time。在仿真环境里所有节点通常都要以仿真时钟为准否则会出现控制器在真实时间下运行、但仿真时间停滞不前的诡异现象。我在第一次跑通之后就是因为use_sim_time设置不一致导致轨迹跟踪误差大得离谱。最后再分享一个小技巧。当你实在不知道插件有没有被加载时可以在 SDF 的 plugin 标签里临时加一个日志参数比如给 plugin 配置一个非法的参数名然后看启动日志有没有相关输出。Gazebo Sim 对未知参数通常会给 warning如果 warning 里出现了你加的参数名说明插件确实在跑只是 ROS2 侧出了问题如果连 warning 都没有那插件可能压根没被加载。这种“故意搞破坏”的定位方法我试过好几次还挺管用的。根据我个人的实际体会Humble 和 Harmonic 这对组合虽然折腾但调通之后确实比老版本舒服很多无论是渲染效果还是物理引擎性能都比 Gazebo Classic 上了一个台阶。如果后面你的项目要上多传感器仿真或者视觉导航Harmonic 配合 ros_gz 的桥接优势会更明显。也希望这篇记录能帮你少走点弯路省下来的时间多喝杯咖啡吧。