blog
•
2026年9月22日
•
70 次阅读
用 MATLAB 遥控老主机上的 Gazebo:一次把仿真链路打通的技术流水账
ros2
matlab
gazebo
仿真
分布式
计算机
---
title: 用 MATLAB 遥控老主机上的 Gazebo:一次把仿真链路打通的技术流水账
date: 2026-09-22
tags: [ROS2, MATLAB, Gazebo, 仿真, 分布式]
---
# 用 MATLAB 遥控老主机上的 Gazebo
## 起因
想做的实验很朴素:**笔记本上跑 MATLAB 做控制,老主机(Linux)跑 Gazebo 场景**,两边用 ROS 2 连起来。先把链路打通,再谈算法。
一路上踩的坑比想象多,记一下流水账。

## 一、链路:MATLAB ↔ ROS 2 ↔ Gazebo
- **老主机**:ROS 2 Jazzy + gz sim 8.10(**headless**,只跑服务端)+ `ros_gz_bridge`(用一个 yaml 逐条写话题映射)+ `foxglove_bridge`(8765)
- **MATLAB 侧**:ROS Toolbox,直接当 ROS 2 节点用
- **Domain ID**:老主机上 `ROS_DOMAIN_ID` **根本没设**,走默认 **0**(进程环境变量、bashrc、启动脚本我都翻过了),MATLAB 侧也设 0 就行
顺手记两个小坑:
1. **关窗口 ≠ 退出 MATLAB**。同一个会话里节点名必须唯一,第二次建同名节点直接报「节点名称必须唯一」。现在我的脚本里节点名带随机数,随便重跑。
2. MATLAB 自带的 **ROS 2 Network Analyzer**(`ros2NetworkAnalyzer`,R2024b 才有)连接时要填 Domain ID;不想开 App 就用命令行:`ros2 node list` / `ros2 topic list`。
## 二、这其实是一套分布式网络(没有中心节点)
跑了半天才意识到:这套东西本质上是个**去中心的分布式网络**,物理拓扑长这样:
```
Wi-Fi 主网段 192.168.1.0/24(路由器 192.168.1.1)
┌──────────────┬──────────────────┬─────────────────┐
MATLAB 笔记本 老主机 .88 PN40 .180 其它设备…
│
│ 有线直连专线 192.168.50.0/24
└────── 老主机 .2 ←→ PN40 .1 ────────┘
```
老主机和 PN40 各有两张网卡,那条 `192.168.50.x` 是两机之间的**直连专线**,不经路由器。
**机制上,ROS 2 没有 master、没有中心服务器**,全靠 DDS 的自动发现:
- 每个节点启动时往**多播地址**广播"我在这、叫什么、有哪些话题"(域 0,UDP 7400–7500 一段);
- 同一网段 + 同一 Domain ID 的节点互相听见 → 自动建立点对点连接(**多播只负责介绍,数据走单播**);
- 所以 MATLAB 一建节点,老主机那边的桥和 gz 节点就自动连上了——**不用配任何 IP**。
这也解释了我为什么只需要确认"Domain ID 是多少":域对了,剩下的它自己会找。
**但"自动"就意味着"不保证"。** 多播有几个天然的脆弱点,今晚的坑基本都在这里:
1. **多网卡选错路**:老主机同时有 `.88` 和 `.2`,多播可能从"错的那张网卡"出去。笔记本只在 Wi-Fi 网段,如果它拿到的是 `192.168.50.x` 的地址,就会**发现成功、数据传不过去**(最难受的一种:看得见连不上)。
2. **Wi-Fi 的 AP 隔离 / 多播过滤**:很多路由器默认掐掉客户端之间的多播,节点互相就"看不见"。
3. **Windows 防火墙**:MATLAB 第一次跑会弹窗,没允许"专用网络"就静默丢包。
兜底招式(按顺序试):
```matlab
setenv('ROS_DOMAIN_ID','0') % 域对上
setenv('ROS_STATIC_PEERS','192.168.1.88') % 绕过多播,直接点名连
% 还不行就两边显式指定 RMW:rmw_fastrtps_cpp
```
**三条通道各有各的脾气**:
| 通道 | 地址 | 特点 |
|---|---|---|
| **DDS(ROS 2)** | 多播发现,动态 | 零配置、自动组网;但依赖多播,跨网段/多网卡容易翻车 |
| **Foxglove** | TCP `老主机:8765` | 点对点、**确定能通**,不受多播影响 |
| **SSH** | TCP `老主机:22` | 控制面(我干活全靠它) |
排障套路也就出来了:**Foxglove 能连 → 网络本身是通的,问题在多播/域;Foxglove 也连不上 → 先查端口和路由。**
想把这套"做稳",可以:固定静态 IP(省得 DHCP 换地址找不着人)、把那条直连专线专门用来跑 DDS(把 Wi-Fi 留给上网),或者干脆两边写死 `ROS_STATIC_PEERS`,彻底不依赖多播。
## 三、桥接:把 gz 的东西变成 ROS 能看的
`ros_gz_bridge` 的配置文件里一条条写映射:`/clock`、`/odom`、位姿信息、`/cmd_vel`……
另外自己写了两个小节点补桥的空白:
- **gz_param_node**:订阅 `/gz_cmd`(字符串 JSON),能 spawn / delete 模型、改重力与物理步长,甚至跑任意 gz 命令,结果回在 `/gz_cmd_result`
- **gz_tf_fix**:直读 gz 的 `pose/info` 文本流,发 `/tf` 和 `/gz_markers`
为什么不用桥自带的 `Pose_V → TFMessage`?因为**实体名会被丢掉**(`frame_id` / `child_frame_id` 全是空)。
## 四、Foxglove 里"只有轴系没有模型"
Foxglove 的 3D 面板不认 Gazebo 的场景几何——要么自己把模型转成 marker,要么就只能看 TF 的轴系。
做法:gz_tf_fix 每 100 ms 取一次位姿,给每个实体画一个 Marker(车身方块 + **绿色车头箭头** + 四个轮子圆柱),Foxglove 里勾上 `/gz_markers`、Fixed frame 设 `world`。
两个坑:
1. **连杆(轮子)在 gz 里给的是"相对车身"的局部位姿**,我一开始当成世界坐标发 → 车跑走了,轮子留在原地。改成「车身世界位姿 ∘ 连杆局部位姿」合成才正常。
2. 未知实体也画方块 → 场景里一堆 0.45 m 的幽灵方块;加了白名单,只画认识的东西。
## 五、造车:从差速走到阿克曼转向
- **v1 差速小车**(DiffDrive):`/cmd_vel` 发速度,车动了,`/odom` 稳定 49 Hz ✓
- **v2 加前轮转向**(AckermannSteering):前轮确实会摆(指令 0.6 rad → 左轮 30.8°、右轮 20.7°,阿克曼差角是对的),**但车身物理上根本不转**:真实航向 0°,一路直线。
这里有个关键教训:**插件报的 odom 是"运动学乐观值"**。它自己算出来"转了 130°",而 Gazebo 场景里的真实位姿是 0°。判断仿真里到底动没动,**要看场景真值(pose/info),不能信插件里程计**——我一开始就被它骗了。
继续排查:给轮子加摩擦(mu 2.0)、换回纯差速驱动,**都不转**。结论是 DART 的轮式接触在侧滑——轮子照转、前轮照摆,但抓不住地,车身拿不到偏航力矩。
**v3 的解法**:车身运动交给 gz 的 **VelocityControl**(直接给模型线速度/角速度,不受打滑影响),前轮摆角继续由阿克曼插件负责。又发现 VelocityControl 的订阅话题是**挂在 link 上**的(`/model/diffbot/link/base_link/cmd_vel`,SDF 里写的 `<topic>` 被忽略),于是在桥的配置里补了一条映射。
结果:
```
v = 0.5 m/s,前轮偏转 0.3 rad(ω = v·tanδ/L = 0.455 rad/s),跑 5 秒
→ 真实航向 +112.7°,位移 2.09 m ✅
→ 前轮摆角 +12.6° / +10.5°
```
## 六、MATLAB 脚本长什么样
就两步:`/cmd_vel` 给**前轮偏转角**(阿克曼插件),`/cmd_vel_kin` 给**车身速度**(VelocityControl),角速度按自行车模型 `ω = v·tanδ/L` 自动换算。
改脚本顶上的 `v_cruise` 和 `delta` 就能玩,节点名带随机数,随便重跑。
## 心得
1. **仿真里"插件说的"不一定是真的**——odom 说转了、场景真值说没转,这种情况真实存在。
2. **先把最小可验证单元做出来**:先让车能直线动起来,再去谈转向。
3. **话题名要对齐作用域**:link 级话题和全局话题不是一回事,桥里必须写全。
4. **分布式靠多播"自动",但自动不等于可靠**:多网卡、Wi-Fi 隔离、防火墙,任何一个都能让你"看得见连不上"。
5. **每次改动都落成脚本**——今晚的定位全靠脚本反复复现,靠手点是查不出来的。

<video controls style="width:100%; max-width:800px;" src="/uploads/2026/9/1790088177309-944695644.mp4"></video>