2.3 插件系统


2.3 插件系统

本节摘要:插件是 gz-sim 架构预留的正式扩展点——编译为动态库、在 SDF 里声明加载、在迭代流水线里获得回调。按挂载位置分世界插件、模型插件、传感器插件与系统插件四类。本节给出选型对照表、一个可编译的最小插件全文,以及插件加载失败的排障路径。

没有插件的世界有多死板

想象一个没有任何插件的 Gazebo:物理会算,画面会显示,但机器人是道具——你发 /cmd_vel 没人理(差速驱动插件没挂)、电池永远是满的、传感器数据发了没人订阅转发。Gazebo 的默认交付形态是一个骨架,行为几乎全靠插件填充。 官方常用插件就是一批"标准行为件":

插件 挂载点 干什么
DiffDrive 模型 订阅速度指令,驱动两轮差速运动学
JointStatePublisher 模型 发布关节状态到总线
JointController / JointPositionController 模型 关节位置/速度命令
IMU / Contact 传感器系统 世界/传感器 产出 IMU 与接触消息
ros_gz_bridge 相关 桥接 gz 消息与 ROS 消息互译

一个典型的移动机器人 SDF 尾部,往往就是一串插件声明:

<plugin filename="gz-sim-diff-drive-system" name="gz::sim::systems::DiffDrive"> <left_joint>left_wheel_joint</left_joint> <right_joint>right_wheel_joint</right_joint> <wheel_separation>0.44</wheel_separation> <wheel_radius>0.12</wheel_radius> <odom_publish_frequency>50</odom_publish_frequency> <topic>/model/robot/cmd_vel</topic> </plugin>

注意这行 SDF 的含义:模型的行为在场景描述里声明,而不是编译进仿真器。同一台机器人,挂 DiffDrive 就是差速车,换成 AckermannSteering 就是阿克曼车——行为件可插拔,正是第 1 章"消息与插件"承诺的落地。

四类插件怎么选

按"你想影响什么"选挂载点:

  • 世界插件(World Plugin):关心全局——回合逻辑、环境随机化、批量生成障碍物。
  • 模型插件(Model Plugin):绑定某个模型——驱动方式、电池管理、关节控制。
  • 传感器插件:处理数据生成路径——自定义传感器噪声、数据格式转换。
  • 系统插件(System,服务器/GUI 级):参与调度本身——自定义物理后处理、性能采样。

一个最小世界插件,全文可编译

需求很典型:世界启动后,每 100 步打一行日志,顺便在迭代回调里读一次某个模型的位姿。世界插件的骨架:

#include <gz/sim/World.hh> #include <gz/sim/System.hh> #include <gz/common/Console.hh> #include <gz/plugin/Register.hh> class StepLogger : public gz::sim::System, public gz::sim::ISystemConfigure, public gz::sim::ISystemPostUpdate { public: void Configure(const gz::sim::Entity & /*entity*/, const std::shared_ptr<const sdf::Element> & /*sdf*/, gz::sim::EntityComponentManager & /*ecm*/, gz::sim::EventManager & /*events*/) override { gzmsg << "StepLogger configured." << std::endl; } // PostUpdate: 物理已写完新状态,适合只读观察 void PostUpdate(const gz::sim::UpdateInfo &info, const gz::sim::EntityComponentManager &ecm) override { if (info.iteration % 100 != 0) return; gzmsg << "sim time: " << std::fixed << std::chrono::duration<double>(info.simTime).count() << " s" << std::endl; // 此处可用 ecm 按名字查询模型实体,读取其 Pose 组件(只读观察) } }; GZ_ADD_PLUGIN(StepLogger, gz::sim::System, gz::sim::ISystemConfigure, gz::sim::ISystemPostUpdate)

三个回调时机的差别,决定你的代码放哪:

回调 时机 适合做
PreUpdate 迭代开始、物理求解前 施加外力、改写命令
Update 系统中段 少用,除非有明确理由
PostUpdate 物理写完新状态后 只读观测、日志、统计

⚠️ 常见坑:在 PostUpdate 里改状态组件,和下一迭代 PreUpdate 的写入打架,出现"速度忽大忽小"的鬼现象。规则很简单——写在 Pre、读在 Post

加载失败的排查顺序:GZ_SIM_SYSTEM_PLUGIN_PATH 是否包含你的动态库目录;SDF 里 filename 是动态库文件名(gz-sim 新版写法如上,Classic 时代是 libxxx.so);name 是否与 GZ_ADD_PLUGIN 注册的类名一致。服务器日志里搜 "Failed to load plugin" 能拿到第一现场。

💡 关键直觉:插件本质是"你向迭代流水线租了一个回调名额"。写插件前先问:这事用现有插件 + 消息能不能拼出来?官方插件覆盖的行为,组合它们的成本远低于自己写 C++。

本节要点回顾

  • 插件 = SDF 声明式加载的动态库:行为与仿真器解耦,同一模型可换行为件。
  • 四类挂载点:世界(全局逻辑)、模型(单体行为)、传感器(数据路径)、系统(调度级)。
  • 回调三时机:PreUpdate 写、PostUpdate 读,别颠倒。
  • 排障三板斧:插件路径环境变量 → filename/name 拼写 → 服务器日志的加载错误。

第 2 章完成了架构层的拆解。第 3 章进入流水线上最重的系统——物理:惯性、接触、关节这些不可回避的硬账,如何记、如何核对。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U