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界面操作(读取键盘按键插件)
- 找到窗口右上角的插件下拉按钮(垂直三点 ⋮ 图标)
- 下拉菜单,点击下拉菜单里的 Key Publisher(按键发布器插件)
- 开启(勾选方框)
之后,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>,作用范围整个仿真场景,不绑定任何单个机器人模型。
优点
- 不依赖小车model,就算删掉vehicle_blue模型,按键触发逻辑依然存在;
- 一套按键控制逻辑,可复用给场景内多台小车;
控制代码放置位置
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、正确方案
- 所有
triggered-publisher按键映射插件,统一放在<world>标签内、所有<model>外部; - 只有差速驱动
diff-drive插件,写在<model name="vehicle_blue">内部; - 二者分工:
- world下TriggeredPublisher:捕获键盘按键,输出
/cmd_vel速度指令; - model内diff-drive:订阅
/cmd_vel,驱动小车轮子运动;
- world下TriggeredPublisher:捕获键盘按键,输出
- 按二者分工,将上述代码分拆成二部分,分别放在
<world>和<model>下。