环境:Ubuntu + ROS 2 Jazzy + RViz2
场景:使用 robot_state_publisher 加载 URDF,并在 RViz2 中通过 RobotModel 查看机械臂模型。

这次问题非常具有迷惑性:URDF、STL、RViz 配置都没有明显错误,但 RViz2 中却会出现两套不同的机器人模型,甚至一个 RobotModel 也会在两套模型之间交替显示。

最终发现,真正原因是:局域网中另一台 ROS 2 设备与本机处于同一个 DDS Domain,并且也在发布 /robot_description

这里记录完整排查过程,希望能给遇到类似问题的人一个参考。


1. 问题描述

我使用 robot_state_publisher 加载一个由 SolidWorks 导出的 URDF,并在 RViz2 中显示机械臂。

正常情况下,希望看到的机器人结构大致为:

base_link
└── link_1
    └── link_2
        └── link_3
            └── link_4
                └── link_5
                    └── link_6

但是实际运行过程中出现了非常奇怪的现象:

  • 有时 RViz2 显示正确的 SolidWorks 模型;
  • 有时却出现另一套带有 shoulderelbowforearm 等命名的模型;
  • STL 有时候看起来像是“加载错了”;
  • 只有一个 RobotModel 时,两套模型甚至会交替出现;
  • 手动再 Add 一个 RobotModel 后,有时两套机器人能够同时出现,但显示结果仍然不稳定。

经过逐项排查后,可以确认以下问题都不是本次异常的原因:

  • URDF 中不存在多余的 shoulderelbowforearm 等 link;
  • STL 文件本身不会生成额外的 ROS TF 坐标系;
  • colcon build--symlink-installinstall/ 中的文件均正常;
  • RViz2 配置中并没有重复保存两个 RobotModel
  • 当前加载的 robot_description 也是正确的 URDF;
  • 问题也不是由 TF tree 本身或 SolidWorks 导出造成的。

排除这些常见原因后,真正值得关注的现象是:只有一个 RobotModel 时,两套模型仍会交替出现。
这说明问题更可能出在 /robot_description 的数据来源,而不是模型文件本身。


2. 检查过程

排除 URDF、STL、RViz 配置和本地 build 问题后,排查过程其实可以很快缩小到 /robot_description

2.1 检查 /robot_description 的发布者

执行:

ros2 topic info /robot_description --verbose

结果发现:

Publisher count: 2

而且两个 publisher 都叫:

Node name: robot_state_publisher
Node namespace: /

但它们的 GID 不同,说明它们实际上是两个不同的发布端点。

这一步基本解释了为什么一个 RViz2 RobotModel 也会在两套机器人之间切换:它订阅的是同一个 /robot_description,但这个 topic 同时存在两份不同的数据源。

robot_state_publisher A ─┐
                         ├── /robot_description ──> RViz2 RobotModel
robot_state_publisher B ─┘

2.2 检查第二个 publisher 是否在本机

随后检查本机进程:

pgrep -af robot_state_publisher

结果本机只有一个 robot_state_publisher

于是出现了一个非常关键的现象:

ROS graph:Publisher count = 2
本机进程:robot_state_publisher = 1

因此可以判断:另一个 publisher 很可能来自局域网中的另一台 ROS 2 设备,而不是当前电脑。

2.3 修改 ROS_DOMAIN_ID 验证

为了验证这一判断,临时切换 ROS 2 Domain:

export ROS_DOMAIN_ID=87
ros2 daemon stop

然后重新启动自己的 ROS 2 launch,再次执行:

ros2 topic info /robot_description --verbose

此时结果变为:

Publisher count: 1

同时 RViz2 中只剩下正确的机器人模型,交替显示的问题完全消失。

至此可以确认:

第二个 /robot_description publisher 来自同一局域网、同一 ROS Domain 中的另一台 ROS 2 设备。


3. 结果

真正的原因是:

局域网中另一台 ROS 2 设备和本机处于相同的 ROS Domain,并且另一台设备也运行着一个名为 /robot_state_publisher 的节点,同时发布 /robot_description

原来的情况可以理解为:

                 同一个局域网
                       │
                ROS_DOMAIN_ID=0
                       │
          ┌────────────┴────────────┐
          │                         │
        本机                     另一台电脑
          │                         │
robot_state_publisher      robot_state_publisher
          │                         │
          └────────┐       ┌────────┘
                   ▼       ▼
              /robot_description
                       │
                       ▼
                    RViz2
                  RobotModel
                       │
               ┌───────┴───────┐
               ▼               ▼
            模型 A            模型 B

修改 Domain 后:

本机                           另一台电脑
ROS_DOMAIN_ID=87               ROS_DOMAIN_ID=0
      │                              │
robot_state_publisher           robot_state_publisher
      │                              │
      X──────── 无法发现 ────────────X

因此:

/robot_description

只剩下一个 publisher,RViz2 也就稳定了。


永久设置 ROS_DOMAIN_ID

如果希望以后都使用某个固定 Domain,可以修改:

nano ~/.bashrc

加入:

export ROS_DOMAIN_ID=87

然后:

source ~/.bashrc

检查:

echo $ROS_DOMAIN_ID

例如:

87

需要注意:

87 并不是 ROS 2 中的特殊数字,只是自己选择的 Domain ID。

同一套 ROS 2 系统中的多台设备应该使用相同 ID。

例如:

机械臂系统 A

视觉 PC:
ROS_DOMAIN_ID=23

轨迹规划 PC:
ROS_DOMAIN_ID=23

控制 PC:
ROS_DOMAIN_ID=23

这样三台机器可以互相发现。

而另外一套实验系统可以使用:

ROS_DOMAIN_ID=42

实现逻辑隔离。


4. 解释

4.1 为什么 ROS 2 会出现这种现象?

这其实并不是 ROS 2 的 bug。

它来自 ROS 2 一个非常重要的设计特性:

ROS 2 是一个支持去中心化、分布式自动发现的通信系统。

ROS 2 通常通过 DDS 进行节点发现和数据通信。

因此,同一个网络中,如果两台机器:

PC A:
ROS_DOMAIN_ID=0

PC B:
ROS_DOMAIN_ID=0

并且 DDS discovery 能够互相发现,那么:

PC A ←──── DDS Discovery ────→ PC B

两台机器上的 ROS 2 节点就可能进入同一个 ROS graph。

这也是为什么:

ros2 node list

不能简单理解成:

“查看本机启动了哪些进程”。

更加准确的理解应该是:

查看当前 ROS Domain 中被发现的 ROS 节点。

4.2 为什么两个节点可以同名?

这次还有一个非常迷惑的地方:

/robot_state_publisher
/robot_state_publisher

两个节点名字完全一样。

但是它们仍然可能对应不同的通信实体。

例如:

Node name: robot_state_publisher
GID: AAAA...

Node name: robot_state_publisher
GID: BBBB...

这里:

GID A != GID B

说明底层存在两个不同的 publisher endpoint。

因此:

节点名字一样

并不能说明:

实际上只有一个 publisher

4.3 为什么一个 Topic 可以有两个 Publisher?

ROS 2 的 publish-subscribe 模型天然允许:

Publisher A ─┐
             │
Publisher B ─┼──→ /topic ───→ Subscriber
             │
Publisher C ─┘

所以两个节点同时发布:

/robot_description

在 ROS 2 通信机制上并不违法。

问题在于:

这两个 publisher 发布的是两套完全不同的 URDF。

而 RViz2 中的 RobotModel 又订阅:

/robot_description

于是就形成:

Publisher A
     │
     ├─────────┐
     │         │
Publisher B    │
     │         │
     └────┬────┘
          ▼
 /robot_description
          │
          ▼
       RViz2
    RobotModel

当 RobotModel 收到两套不同机器人描述时,就可能表现成:

模型 A
  ↓
模型 B
  ↓
模型 A
  ↓
...

这正是这次看到的“幽灵模型”。


4.4 ros2 daemon 是不是导致问题的原因?

不是。

ros2 daemon 主要是帮助 ROS 2 CLI 获取和维护 graph 信息。

例如:

ros2 node list
ros2 topic list
ros2 service list

会使用相关的 graph 信息。

真正导致另一台电脑进入当前 ROS graph 的核心是:

DDS discovery
+
相同的 ROS_DOMAIN_ID

而不是 daemon。

之所以切换 Domain 后经常执行:

ros2 daemon stop

主要是为了让 CLI 后台进程重新在新的环境配置下工作。

如果已经把:

export ROS_DOMAIN_ID=87

写进:

~/.bashrc

那么以后打开新的终端,通常不需要每次都:

ros2 daemon stop

4.5 一套比较实用的 ROS 2“幽灵节点”排查流程

以后如果再次遇到:

  • RViz 莫名出现另一套机器人;
  • 不属于自己的 TF;
  • 一个 RobotModel 在不同模型之间切换;
  • Topic 数据来源不稳定;
  • 出现本机根本没有启动的 ROS node;

可以优先按照下面的顺序检查。

① 查看 ROS graph

ros2 node list

② 查看关键 Topic 的发布者

例如:

ros2 topic info /robot_description --verbose

重点关注:

Publisher count
Node name
Node namespace
GID

③ 检查 TF publisher

ros2 topic info /tf --verbose
ros2 topic info /tf_static --verbose

④ 检查本机进程

pgrep -af robot_state_publisher

如果:

ROS graph 中有两个 publisher

但是:

本机只有一个进程

那么就应该高度怀疑:

第二个 publisher 来自其他机器。

⑤ 查看 Domain

echo $ROS_DOMAIN_ID

⑥ 临时切换 Domain 做验证

export ROS_DOMAIN_ID=87

必要时:

ros2 daemon stop

然后重新启动自己的 ROS 2 系统。

如果:

ros2 topic info /robot_description --verbose

从:

Publisher count: 2

变成:

Publisher count: 1

问题基本就确认了。


最后总结

在 ROS 2 中,“本机运行了哪些节点”和“当前 ROS graph 中有哪些节点”是两个不同的问题。

ROS 2 本身就是面向分布式、多机通信设计的。

因此在多人共用局域网的实验室中,如果大家都使用默认 ROS Domain,就可能出现非常隐蔽的跨机器干扰。

上文借助Agent整理完成。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐