科普:Gazebo中仿真小车的键盘控制

Gazebo中仿真小车的键盘控制,通过监听键盘按键 /keyboard/keypress,发布 /cmd_vel 话题,控制小车运动。

那么,键盘有两套编码体系,一是Qt功能键编码,二是ASCII码,这两套编码体系不能混用。

一、 两种编码区别

1、字母ASCII码(你按i得到73)

  • 普通字母 I/i:ASCII值固定 73
  • 仅用于可打印字符(字母、数字、逗号、空格)
  • 适用场景:你监听 /keyboard/keypress 按字母I打印73

2 Qt功能键编码(16777xxx 大数)

Gazebo KeyPublisher底层基于Qt框架,方向键、ESC、F1-F12这类无ASCII字符的功能键,统一用16777开头大数字编码:

  • 方向上下左右 = 16777235 / 16777237 / 16777234 / 16777236
  • 数字小键盘(并不是按次序的):0 = 16777222,...,9 = 16777238
  • 字母键不会输出这类大数编码,比如 I 输出 73

3. 验证键值

第一个终端:启动仿真

bash 复制代码
gz sim my_robot.sdf

作用:加载小车SDF仿真文件,打开gz-sim图形界面。

GUI界面操作(读取键盘按键插件)

  1. 找到窗口右上角的插件下拉按钮(垂直三点 ⋮ 图标)
  2. 下拉菜单,点击下拉菜单里的 Key Publisher(按键发布器插件)
  3. 开启(勾选方框)
    之后,GUI窗口捕获键盘敲击动作,并把按键信息发布到 /keyboard/keypress 话题。

新开第二个终端:监听键盘按键消息

bash 复制代码
gz topic -e -t /keyboard/keypress

只要你点击激活gz-sim仿真窗口、按下任意键盘按键,这个终端会实时打印你按下的键值、按键名称等原始消息。

按键生效前提

必须先用鼠标点击仿真GUI窗口,让窗口处于激活焦点状态,键盘输入才会被Key Publisher捕获;焦点在终端时按键不会被采集。

效果

  • GUI界面按字母I → 第二个终端上显示 data:73
  • GUI界面按向上方向键 → 第二个终端上显示 data:16777235
    一目了然区分两套编码。

二、 两套控制方案二选一(不能混用)

上述两套编码,对应两套控制方案,但必须二选一进行配套,多个小车可用多套,各套的方案应不冲突。

方案A:用方向键控制

上下左右方向键操控小车:

  • ↑ 前进、↓后退、←左转、→右转
  • 也可以用数字小键盘 控制。
    如下,以方向键为例:
xml 复制代码
<!-- 1. 前进 -->
<plugin filename="gz-sim-triggered-publisher-system"
        name="gz::sim::systems::TriggeredPublisher">
    <input type="gz.msgs.Int32" topic="/keyboard/keypress">
        <match field="data">16777235</match>
    </input>
    <output type="gz.msgs.Twist" topic="/cmd_vel">
        linear: {x: 0.5}, angular: {z: 0.0}
    </output>
</plugin>

<!-- 2. 后退 -->
<plugin filename="gz-sim-triggered-publisher-system"
        name="gz::sim::systems::TriggeredPublisher">
    <input type="gz.msgs.Int32" topic="/keyboard/keypress">
        <match field="data">16777237</match>
    </input>
    <output type="gz.msgs.Twist" topic="/cmd_vel">
        linear: {x: -0.5}, angular: {z: 0.0}
    </output>
</plugin>

<!-- 3. 左转 -->
<plugin filename="gz-sim-triggered-publisher-system"
        name="gz::sim::systems::TriggeredPublisher">
    <input type="gz.msgs.Int32" topic="/keyboard/keypress">
        <match field="data">16777234</match>
    </input>
    <output type="gz.msgs.Twist" topic="/cmd_vel">
        linear: {x: 0.0}, angular: {z: 0.4}
    </output>
</plugin>

<!-- 4. 右转 -->
<plugin filename="gz-sim-triggered-publisher-system"
        name="gz::sim::systems::TriggeredPublisher">
    <input type="gz.msgs.Int32" topic="/keyboard/keypress">
        <match field="data">16777236</match>
    </input>
    <output type="gz.msgs.Twist" topic="/cmd_vel">
        linear: {x: 0.0}, angular: {z: -0.4}
    </output>
</plugin>

方案B:改成字母 I/J/L/, 控制(可调整你习惯按字母)

把上述代码中 match 里的大数替换成ASCII字母编码:

xml 复制代码
<!-- I 前进,ASCII=73 -->
<plugin filename="gz-sim-triggered-publisher-system"
        name="gz::sim::systems::TriggeredPublisher">
    <input type="gz.msgs.Int32" topic="/keyboard/keypress">
        <match field="data">73</match>
    </input>
    <output type="gz.msgs.Twist" topic="/cmd_vel">
        linear: {x: 0.5}, angular: {z: 0.0}
    </output>
</plugin>

<!-- J 左转 ASCII=74 -->
<plugin filename="gz-sim-triggered-publisher-system"
        name="gz::sim::systems::TriggeredPublisher">
    <input type="gz.msgs.Int32" topic="/keyboard/keypress">
        <match field="data">74</match>
    </input>
    <output type="gz.msgs.Twist" topic="/cmd_vel">
        linear: {x: 0.0}, angular: {z: 0.4}
    </output>
</plugin>

<!-- L 右转 ASCII=76 -->
<plugin filename="gz-sim-triggered-publisher-system"
        name="gz::sim::systems::TriggeredPublisher">
    <input type="gz.msgs.Int32" topic="/keyboard/keypress">
        <match field="data">76</match>
    </input>
    <output type="gz.msgs.Twist" topic="/cmd_vel">
        linear: {x: 0.0}, angular: {z: -0.4}
    </output>
</plugin>

<!-- , 逗号后退 ASCII=44 -->
<plugin filename="gz-sim-triggered-publisher-system"
        name="gz::sim::systems::TriggeredPublisher">
    <input type="gz.msgs.Int32" topic="/keyboard/keypress">
        <match field="data">44</match>
    </input>
    <output type="gz.msgs.Twist" topic="/cmd_vel">
        linear: {x: -0.5}, angular: {z: 0.0}
    </output>
</plugin>

<!-- K 停车 ASCII=75 -->
<plugin filename="gz-sim-triggered-publisher-system"
        name="gz::sim::systems::TriggeredPublisher">
    <input type="gz.msgs.Int32" topic="/keyboard/keypress">
        <match field="data">75</match>
    </input>
    <output type="gz.msgs.Twist" topic="/cmd_vel">
        linear: {x: 0.0}, angular: {z: 0.0}
    </output>
</plugin>

三、放在仿真文件SDF中哪里

控制代码插件两种合法放置位置、适用场景

1、控制代码放在 <world>

规则

<plugin filename="gz-sim-triggered-publisher-system">世界级系统插件 ,父级允许是 <world>,作用范围整个仿真场景,不绑定任何单个机器人模型。

优点

  1. 不依赖小车model,就算删掉vehicle_blue模型,按键触发逻辑依然存在;
  2. 一套按键控制逻辑,可复用给场景内多台小车;

控制代码放置位置

xml 复制代码
<world name="car_world">
  <physics>...</physics>
  <!-- 世界系统插件:物理、UI广播等 -->
  <plugin filename="gz-sim-physics-system" ...></plugin>
  <plugin filename="gz-sim-user-commands-system" ...></plugin>
  <plugin filename="gz-sim-scene-broadcaster-system" ...></plugin>

  <!-- 按键触发发布器:放在world下,和物理插件平级 -->
  <!-- 前进I -->
  <plugin filename="gz-sim-triggered-publisher-system"
          name="gz::sim::systems::TriggeredPublisher">
      <input type="gz.msgs.Int32" topic="/keyboard/keypress">
          <match field="data">16777235</match>
      </input>
      <output type="gz.msgs.Twist" topic="/cmd_vel">
          linear: {x: 0.5}, angular: {z: 0.0}
      </output>
  </plugin>
  <!-- 后退、左转、右转、停止插件依次放这里 -->

  <light>...</light>
  <model name="ground_plane">...</model>
  <model name="vehicle_blue">
    <!-- 这里只放小车专属插件:diff-drive差速驱动 -->
    <plugin filename="gz-sim-diff-drive-system" ...></plugin>
  </model>
</world>

2、控制代码放在 <model> 内部(模型级插件,局部绑定小车)

规则

只有模型专属系统插件 才能写在<model>内:

  • 多台小车时,每个model都会生成一套按键监听,按键冲突;
  • 销毁小车模型,按键控制直接失效;

适用场景

仅当场景里有多台独立小车、需要不同按键分别控制每台车时才使用,单小车完全没必要。

3、正确方案

  1. 所有 triggered-publisher 按键映射插件,统一放在 <world> 标签内、所有<model>外部;
  2. 只有差速驱动 diff-drive 插件,写在 <model name="vehicle_blue"> 内部;
  3. 二者分工:
    • world下TriggeredPublisher:捕获键盘按键,输出 /cmd_vel 速度指令;
    • model内diff-drive:订阅 /cmd_vel,驱动小车轮子运动;
  4. 按二者分工,将上述代码分拆成二部分,分别放在 <world><model> 下。