话题 Topic 发布订阅:从代码到 rqt 可视化验证
在 ROS 2 中,Topic 是最常用的通信方式之一。传感器持续上报数据、控制器周期发送状态、导航模块发布位姿,这些场景通常都适合使用发布订阅模型。
很多初学者能够照着示例写出 Publisher 和 Subscriber,却很难判断系统是否真的跑通:发布节点在打印日志,是否代表消息已经进入 Topic?订阅节点没有输出,是 Topic 名称不一致、消息类型不匹配,还是 QoS 不兼容?rqt_graph 里出现了两条连线,又分别表示什么?
本文使用 Python 和 rclpy 创建一个最小发布订阅示例:发布节点每 0.5 秒向 /counter 发布一个递增整数,订阅节点接收并打印该整数。随后依次使用 ROS 2 命令行、rqt_graph、rqt_topic 和 rqt_plot 验证节点、Topic、消息内容、通信关系与数据变化。
完成本文后,应当能够独立完成以下工作:
- 创建一个 ROS 2 Python 软件包。
- 编写并运行 Publisher 与 Subscriber。
- 使用命令行确认 Topic 名称、类型、频率和端点数量。
- 使用 rqt 查看通信拓扑、实时消息和数值曲线。
- 根据现象定位名称、类型、QoS、环境或图形界面问题。
1. 版本与前置条件
| 项目 | 本文目标 |
|---|---|
| ROS 2 主要示例 | Humble |
| ROS 2 兼容版本 | Jazzy |
| Humble 系统 | Ubuntu 22.04 |
| Jazzy 系统 | Ubuntu 24.04 |
| 编程语言 | Python 3 |
| 客户端库 | rclpy |
| 消息类型 | std_msgs/msg/Int32 |
| 构建工具 | colcon + ament_python |
| 可视化工具 | rqt_graph、rqt_topic、rqt_plot |
Humble 和 Jazzy 在本文使用的 rclpy、Topic 命令以及 rqt 基本操作上基本一致。界面布局可能略有差异,但验证思路不变。
开始前需要满足以下条件:
- 已安装 ROS 2 Humble 或 Jazzy。
- 使用 Bash 终端执行命令。
- 已安装
colcon、rclpy、std_msgs和 rqt 工具。 - 当前用户能够在主目录中创建工作空间。
- 系统具有可用的桌面图形环境。
先加载 ROS 2 环境。本文以 Humble 为例:
source /opt/ros/humble/setup.bash
如果使用 Jazzy,则执行:
source /opt/ros/jazzy/setup.bash
检查当前环境:
printenv ROS_DISTRO
printenv ROS_VERSION
which ros2
which colcon
正常情况下,ROS_VERSION 应为 2,ROS_DISTRO 应显示当前发行版名称。
安装本文所需工具前,先确认 ROS_DISTRO 已经正确设置:
test -n "$ROS_DISTRO" || {
echo "ROS_DISTRO 未设置,请先 source /opt/ros/<发行版>/setup.bash"
exit 1
}
如果系统使用的是官方 ROS 软件源,可以直接安装:
sudo apt update
sudo apt install \
-y \
python3-colcon-common-extensions \
ros-$ROS_DISTRO-rclpy \
ros-$ROS_DISTRO-std-msgs \
ros-$ROS_DISTRO-rqt \
ros-$ROS_DISTRO-rqt-common-plugins \
ros-$ROS_DISTRO-rqt-graph \
ros-$ROS_DISTRO-rqt-topic \
ros-$ROS_DISTRO-rqt-plot
不同安装方式可能已经包含其中一部分软件包。APT 提示已安装时无需重复处理。
1.1 遇到 404 或 EXPKEYSIG 时修复 ROS 软件源
如果 apt update 或 apt install 出现以下信息:
404 Not Found
EXPKEYSIG F42ED6FBAB17C654 Open Robotics
通常表示当前镜像的包索引过期、镜像同步不完整,或者本机保存了旧的 ROS 软件源签名密钥。此时不要反复执行 apt-get --fix-missing,先切换到官方 ROS 软件源并刷新索引。
以下命令适用于 Ubuntu 22.04 + ROS 2 Humble。执行前请确认系统确实是 Ubuntu:
test -f /etc/os-release
. /etc/os-release
test "$ID" = "ubuntu" || {
echo "本文命令只适用于 Ubuntu"
exit 1
}
test "$VERSION_CODENAME" = "jammy" || {
echo "本文 Humble 示例需要 Ubuntu 22.04 jammy"
exit 1
}
删除旧的 ROS 软件源配置和 key:
sudo rm -f /etc/apt/sources.list.d/ros2.list
sudo rm -f /usr/share/keyrings/ros-archive-keyring.gpg
重新安装基础工具并导入当前 ROS key:
sudo apt install -y curl ca-certificates gnupg
sudo curl -fsSL \
https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
-o /usr/share/keyrings/ros-archive-keyring.gpg
重新写入官方 ROS 2 软件源:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu jammy main" \
| sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
清理旧索引后重新更新:
sudo apt clean
sudo rm -rf /var/lib/apt/lists/*
sudo apt update
确认候选版本已经来自可用的软件源:
apt-cache policy ros-humble-rqt-topic
apt-cache policy ros-humble-rqt-plot
如果 Candidate 显示为具体版本,而不是 (无),再执行本文的安装命令:
sudo apt install -y \
python3-colcon-common-extensions \
ros-humble-rclpy \
ros-humble-std-msgs \
ros-humble-rqt \
ros-humble-rqt-common-plugins \
ros-humble-rqt-graph \
ros-humble-rqt-topic \
ros-humble-rqt-plot
如果所在网络无法访问 packages.ros.org 或 GitHub,应使用组织提供的网络代理,或选择能够保持完整同步和有效签名的 ROS 镜像。不要只把域名替换成另一个地址,却继续沿用旧的 /var/lib/apt/lists 和旧 key;否则仍可能得到相同的 404 或签名错误。
Jazzy 用户应将上面的 Ubuntu 版本和软件包前缀替换为对应的 noble 与 ros-jazzy-*,不要在同一台终端混用 Humble 和 Jazzy 的软件源。
2. 先理解 Topic 发布订阅模型
Topic 通信包含三个核心对象:
| 对象 | 本文示例 | 作用 |
|---|---|---|
| Publisher | /counter_publisher |
产生并发布消息 |
| Topic | /counter |
连接发布端与订阅端的命名数据通道 |
| Subscriber | /counter_subscriber |
接收并处理消息 |
完整数据链路如下:
定时器每 0.5 秒触发
-> counter_publisher 创建 Int32 消息
-> Publisher 向 /counter 发布
-> DDS 在兼容端点之间传输消息
-> counter_subscriber 的回调函数被触发
-> 终端打印 Received: <数值>
发布者和订阅者不直接保存对方的地址,也不要求按照固定顺序启动。它们通过相同的 Topic 名称、消息类型以及兼容的 QoS 自动建立通信。
要让一次 Topic 通信成功,至少需要同时满足:
- 两端处于能够互相发现的 ROS 图中。
- 最终解析后的 Topic 名称一致。
- 消息类型完全一致。
- 发布端和订阅端的 QoS 兼容。
- 发布节点确实在执行发布逻辑。
- 订阅节点仍在运行并能够执行回调。
本文选择 std_msgs/msg/Int32,是因为它结构简单,既可以用 ros2 topic echo 查看,也可以直接在 rqt_plot 中绘制 /counter/data。
3. 创建工作空间和软件包
创建名为 topic_ws 的工作空间:
mkdir -p ~/topic_ws/src
cd ~/topic_ws/src
创建 Python 软件包:
ros2 pkg create topic_demo \
--build-type ament_python \
--license Apache-2.0 \
--description "ROS 2 topic publisher and subscriber demo" \
--maintainer-name ubuntu \
--maintainer-email ubuntu@example.com \
--dependencies rclpy std_msgs
如果已经使用旧命令创建了软件包,不需要删除目录重来。打开 package.xml,将:
<license>TODO: License declaration</license>
改为:
<license>Apache-2.0</license>
警告中的 TODO 是占位许可证,不会阻止构建;但正式项目还应在软件包根目录补充对应的 LICENSE 文件。
创建后的核心目录结构应类似:
topic_ws/
└── src/
└── topic_demo/
├── package.xml
├── resource/
│ └── topic_demo
├── setup.cfg
├── setup.py
├── test/
└── topic_demo/
└── __init__.py
外层 topic_demo 是 ROS 2 软件包目录,内层 topic_demo 是 Python 模块目录。Publisher 和 Subscriber 的源文件都应放在内层目录中。
4. 编写发布节点
创建文件:
~/topic_ws/src/topic_demo/topic_demo/counter_publisher.py
内容如下:
import rclpy
from rclpy.node import Node
from std_msgs.msg import Int32
class CounterPublisher(Node):
def __init__(self):
super().__init__('counter_publisher')
self.publisher = self.create_publisher(Int32, 'counter', 10)
self.timer = self.create_timer(0.5, self.publish_counter)
self.counter = 0
def publish_counter(self):
message = Int32()
message.data = self.counter
self.publisher.publish(message)
self.get_logger().info(f'Publishing: {message.data}')
self.counter += 1
def main(args=None):
rclpy.init(args=args)
node = CounterPublisher()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
这段代码完成了五件事:
- 创建名为
counter_publisher的节点。 - 创建消息类型为
Int32的 Publisher。 - 使用相对名称
counter创建 Topic。 - 每 0.5 秒执行一次定时器回调。
- 发布递增整数并记录日志。
create_publisher(Int32, 'counter', 10) 中的三个参数分别表示消息类型、Topic 名称和 QoS 队列深度。没有设置命名空间时,相对名称 counter 最终解析为 /counter。
5. 编写订阅节点
创建文件:
~/topic_ws/src/topic_demo/topic_demo/counter_subscriber.py
内容如下:
import rclpy
from rclpy.node import Node
from std_msgs.msg import Int32
class CounterSubscriber(Node):
def __init__(self):
super().__init__('counter_subscriber')
self.subscription = self.create_subscription(
Int32,
'counter',
self.counter_callback,
10,
)
def counter_callback(self, message):
self.get_logger().info(f'Received: {message.data}')
def main(args=None):
rclpy.init(args=args)
node = CounterSubscriber()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
订阅回调不会由代码主动调用。节点执行 rclpy.spin(node) 后,执行器持续等待事件;当 /counter 收到兼容的 Int32 消息时,ROS 2 才会调用 counter_callback()。
将订阅对象保存在 self.subscription 中也很重要。如果只创建对象而不保留引用,它可能被 Python 回收,导致订阅失效。
6. 注册可执行入口
打开:
~/topic_ws/src/topic_demo/setup.py
将 entry_points 配置为:
entry_points={
'console_scripts': [
'counter_publisher = topic_demo.counter_publisher:main',
'counter_subscriber = topic_demo.counter_subscriber:main',
],
},
完整核心配置可写为:
from setuptools import find_packages, setup
package_name = 'topic_demo'
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
(
'share/ament_index/resource_index/packages',
['resource/' + package_name],
),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='user',
maintainer_email='user@example.com',
description='ROS 2 topic publisher and subscriber demo',
license='Apache-2.0',
entry_points={
'console_scripts': [
'counter_publisher = topic_demo.counter_publisher:main',
'counter_subscriber = topic_demo.counter_subscriber:main',
],
},
)
两条入口配置的含义如下:
| 可执行名称 | Python 模块 | 启动函数 |
|---|---|---|
counter_publisher |
topic_demo.counter_publisher |
main |
counter_subscriber |
topic_demo.counter_subscriber |
main |
ros2 run 不会自动扫描软件包中的所有 .py 文件。只有正确安装并注册的入口,才能作为 ROS 2 可执行程序被发现。
7. 构建并运行发布订阅节点
先检查 Python 语法:
cd ~/topic_ws
python3 -m py_compile \
src/topic_demo/topic_demo/counter_publisher.py \
src/topic_demo/topic_demo/counter_subscriber.py
确认 colcon 能够发现软件包:
colcon list
预期输出中包含:
topic_demo src/topic_demo (python)
构建目标软件包:
colcon build \
--symlink-install \
--packages-select topic_demo
构建成功后加载工作空间:
source install/setup.bash
确认两个可执行入口已经安装:
ros2 pkg prefix topic_demo
ros2 pkg executables topic_demo
预期至少显示:
topic_demo counter_publisher
topic_demo counter_subscriber
打开终端 A,运行发布节点:
source /opt/ros/humble/setup.bash
source ~/topic_ws/install/setup.bash
ros2 run topic_demo counter_publisher
预期输出类似:
[INFO] [counter_publisher]: Publishing: 0
[INFO] [counter_publisher]: Publishing: 1
[INFO] [counter_publisher]: Publishing: 2
打开终端 B,运行订阅节点:
source /opt/ros/humble/setup.bash
source ~/topic_ws/install/setup.bash
ros2 run topic_demo counter_subscriber
预期输出类似:
[INFO] [counter_subscriber]: Received: 8
[INFO] [counter_subscriber]: Received: 9
[INFO] [counter_subscriber]: Received: 10
订阅节点通常从启动之后的新消息开始接收,不会自动获得启动前已经发布的历史数据。本文使用默认 Volatile Durability,这种行为是正常的。
8. 使用命令行完成第一轮验证
在打开 rqt 之前,先通过命令行确认底层通信正常。这样可以把“ROS 2 通信问题”和“图形界面问题”分开处理。
8.1 查看节点
新开终端 C,并加载环境:
source /opt/ros/humble/setup.bash
source ~/topic_ws/install/setup.bash
ros2 node list
预期看到:
/counter_publisher
/counter_subscriber
查看发布节点信息:
ros2 node info /counter_publisher
输出中的 Publishers 应包含 /counter: std_msgs/msg/Int32。
查看订阅节点信息:
ros2 node info /counter_subscriber
输出中的 Subscribers 应包含 /counter: std_msgs/msg/Int32。
8.2 查看 Topic 名称和类型
ros2 topic list -t
ros2 topic type /counter
预期结果:
std_msgs/msg/Int32
8.3 查看端点和 QoS
ros2 topic info /counter -v
正常情况下应看到:
Type: std_msgs/msg/Int32
Publisher count: 1
Subscription count: 1
详细输出还会列出每个端点的节点名称、命名空间、GID 和 QoS。本文两端均使用深度为 10 的默认可靠 QoS,因此应当兼容。
8.4 查看实时消息
ros2 topic echo /counter
输出类似:
data: 23
---
data: 24
---
data: 25
---
只接收一条消息后退出:
ros2 topic echo /counter --once
8.5 检查发布频率
ros2 topic hz /counter
由于定时器周期为 0.5 秒,理论频率约为 2 Hz。实际输出可能略有波动:
average rate: 2.000
8.6 绕过代码测试订阅端
可以暂时停止 Python 发布节点,然后由命令行发布一条消息:
ros2 topic pub --once \
/counter \
std_msgs/msg/Int32 \
"{data: 100}"
订阅终端应显示:
[INFO] [counter_subscriber]: Received: 100
这项测试能够单独验证订阅节点是否正常。
8.7 绕过代码测试发布端
重新启动 Python 发布节点,停止订阅节点,然后执行:
ros2 topic echo /counter --once
如果能够收到数据,说明发布节点本身正常。把发布端和订阅端分开验证,通常比同时修改两份代码更容易定位故障。
9. 使用 rqt_graph 查看通信拓扑
确保发布节点和订阅节点都在运行,然后在新终端执行:
source /opt/ros/humble/setup.bash
source ~/topic_ws/install/setup.bash
rqt_graph
也可以先启动 rqt 主界面:
rqt
再通过菜单打开:
Plugins -> Introspection -> Node Graph
正常情况下,图中应当形成以下关系:
/counter_publisher -> /counter -> /counter_subscriber
下面是实际运行 rqt_graph 后的界面截图。可以直接对照检查三个节点和 /counter Topic 是否已经连通:
在 rqt_graph 中:
- 椭圆或节点框通常表示 ROS 2 节点。
- Topic 名称是否单独显示,取决于当前图形模式。
- 箭头方向表示消息从发布者流向订阅者。
- 隐藏 Debug 项后,
/rosout和参数相关接口可能不再显示。
如果图中过于杂乱,可以调整左上角的显示选项,并取消显示无关节点。修改筛选选项后,点击刷新按钮重新读取 ROS 图。
rqt_graph 能证明端点关系已经建立,但不能证明消息内容一定正确。例如发布节点可能已创建 Publisher,却因为定时器回调异常而没有持续发布。因此仍需结合 ros2 topic echo、rqt_topic 或 rqt_plot 验证数据。
10. 使用 rqt_topic 查看实时消息
在新终端执行:
source /opt/ros/humble/setup.bash
source ~/topic_ws/install/setup.bash
rqt_topic
也可以从 rqt 主界面打开:
Plugins -> Topics -> Topic Monitor
在 Topic 列表中找到 /counter,展开后应看到字段:
/counter
└── data
勾选 /counter 左侧的复选框后,工具会订阅该 Topic,data 值应随着发布节点持续递增。界面通常还会显示消息类型和带宽等信息。
如果列表中没有 /counter:
- 点击刷新按钮。
- 确认发布节点仍在运行。
- 在同一终端检查
ros2 topic list。 - 检查 rqt 终端是否加载了正确的 ROS 2 环境。
- 比较 rqt 与节点终端中的
ROS_DOMAIN_ID。
如果能看到 /counter,但数值不变化,应进一步检查:
ros2 topic hz /counter
ros2 topic echo /counter --once
命令行有数据而 rqt 没有更新时,问题通常位于 rqt 插件、缓存或图形环境;命令行也没有数据时,应回到发布节点和 Topic 配置继续排查。
11. 使用 rqt_plot 绘制数值曲线
rqt_plot 适合观察数值字段随时间的变化。启动工具:
source /opt/ros/humble/setup.bash
source ~/topic_ws/install/setup.bash
rqt_plot
也可以从 rqt 主界面打开:
Plugins -> Visualization -> Plot
在 Topic 输入框中输入:
/counter/data
然后点击添加按钮。正常情况下会看到一条持续上升的阶梯状或近似斜线曲线。
这里必须填写消息字段路径 /counter/data,而不是只填写 /counter。原因是 rqt_plot 绘制的是消息中的数值字段;Int32 消息结构为:
int32 data
可以用以下命令确认接口定义:
ros2 interface show std_msgs/msg/Int32
如果将定时器周期从 0.5 修改为 0.1,重新运行后曲线上升会更快,同时:
ros2 topic hz /counter
测得的频率应由约 2 Hz 变为约 10 Hz。这说明代码参数、命令行观测和图形曲线能够互相验证。
12. 四类验证结果应该如何对应
| 验证工具 | 主要证明什么 | 不能单独证明什么 |
|---|---|---|
ros2 node list |
节点已加入 ROS 图 | 节点一定正在发布有效消息 |
ros2 topic info -v |
Topic 类型、端点和 QoS 信息 | 回调逻辑一定正确 |
ros2 topic echo / rqt_topic |
Topic 中确实有消息 | 数值变化趋势是否符合预期 |
rqt_graph |
节点与 Topic 的连接关系 | 消息字段内容正确 |
ros2 topic hz |
消息到达频率 | 每条消息的数据值正确 |
rqt_plot |
数值字段随时间的变化趋势 | 非数值字段的完整内容 |
一次完整验证不应只看某一个窗口。推荐的最小闭环是:
节点可见
-> Topic 可见且类型正确
-> 发布与订阅端点数量正确
-> echo 能收到消息
-> rqt_graph 连接方向正确
-> rqt_topic 数值实时更新
-> rqt_plot 曲线符合预期
13. Topic 名称、类型和 QoS
13.1 相对名称与绝对名称
代码中使用的是相对名称:
self.create_publisher(Int32, 'counter', 10)
在根命名空间下,它解析为 /counter。如果启动节点时加入命名空间:
ros2 run topic_demo counter_publisher \
--ros-args \
-r __ns:=robot1
最终 Topic 会变为:
/robot1/counter
此时订阅节点仍在根命名空间运行,它订阅的是 /counter,两端不会连接。可以让订阅节点使用相同命名空间:
ros2 run topic_demo counter_subscriber \
--ros-args \
-r __ns:=robot1
或者显式重映射:
ros2 run topic_demo counter_subscriber \
--ros-args \
-r counter:=/robot1/counter
不要只看源代码中的字符串,要使用以下命令检查最终名称:
ros2 topic list
ros2 node info /robot1/counter_publisher
13.2 消息类型必须完全一致
如果发布端使用 std_msgs/msg/Int32,订阅端改成 std_msgs/msg/String,即使 Topic 名称都叫 /counter,也无法建立正常的数据连接。
检查类型:
ros2 topic type /counter
ros2 topic info /counter -v
不要根据字段看起来相似就认为类型兼容。ROS 2 使用完整接口类型匹配端点。
13.3 QoS 必须兼容
本文的 10 是常见简写,表示使用深度为 10 的默认 QoS 配置。对于传感器数据、弱网络或需要历史消息的场景,项目可能会使用不同的 Reliability、Durability、History 和 Depth。
常见不兼容场景是:发布者只提供 Best Effort,而订阅者要求 Reliable。此时双方可能出现在 ROS 图中,却无法正常传输消息。
查看端点 QoS:
ros2 topic info /counter -v
排查时应比较实际端点信息,不要只比较队列深度。
14. 五个典型问题速览
| 问题 | 典型表现 | 优先检查 |
|---|---|---|
| 环境未加载 | ros2、软件包或 rqt 命令找不到 |
ROS_DISTRO、setup.bash |
| 可执行入口未安装 | No executable found |
setup.py、重新构建 |
| Topic 名称不一致 | 两个节点都运行但无连接 | node info、命名空间、重映射 |
| 类型或 QoS 不匹配 | Topic 可见但收不到消息 | topic info -v |
| rqt 没有图或数据 | 命令行正常,GUI 不刷新 | rqt 环境、插件、刷新、显示环境 |
15. 坑一:两个终端没有加载相同环境
15.1 错误表现
终端 A 能运行发布节点,终端 B 却出现:
Package 'topic_demo' not found
或者 rqt 中完全看不到目标节点和 Topic。
15.2 原因分析
每个终端都有独立环境。终端 A 执行过 source ~/topic_ws/install/setup.bash,不会自动影响终端 B。不同终端还可能使用不同的 ROS_DOMAIN_ID 或不同 ROS 2 发行版。
15.3 解决步骤
每个新终端都执行:
source /opt/ros/humble/setup.bash
source ~/topic_ws/install/setup.bash
然后比较:
printenv ROS_DISTRO
printenv ROS_DOMAIN_ID
printenv RMW_IMPLEMENTATION
ros2 pkg prefix topic_demo
同一台电脑上的简单实验,应先让各终端使用相同的发行版、Domain ID 和 RMW 实现。
16. 坑二:修改代码或 setup.py 后运行的仍是旧版本
16.1 错误表现
- 新增的可执行入口找不到。
- 修改定时器周期后频率没有变化。
- 日志内容仍然是旧文本。
rqt_plot曲线与代码中的新逻辑不一致。
16.2 原因分析
ros2 run 启动的是安装空间中的程序入口。使用 --symlink-install 后,普通 Python 源文件修改通常可以通过符号链接立即反映,但 setup.py、入口点和安装规则发生变化时仍需重新构建。旧进程不重启也不会自动加载新代码。
16.3 解决步骤
cd ~/topic_ws
colcon build \
--symlink-install \
--packages-select topic_demo
source install/setup.bash
ros2 pkg executables topic_demo
停止旧节点,再重新运行。必要时检查包的实际来源:
ros2 pkg prefix topic_demo
不要一遇到问题就删除整个工作空间。先重新构建目标包并确认环境,只有在包被移动、重命名或安装元数据明显混乱时,才考虑清理该工作空间的 build、install 和 log 后重建。
17. 坑三:节点都在运行,但 Topic 名称不同
17.1 错误表现
ros2 node list
能够看到两个节点,但订阅终端没有输出,rqt_graph 中两者之间也没有连接。
17.2 详细检查
ros2 node info /counter_publisher
ros2 node info /counter_subscriber
ros2 topic list -t
重点比较 Publisher 和 Subscriber 的最终 Topic 名称。常见差异包括:
/counter与/counters拼写不同。- 一个节点位于
/robot1命名空间,另一个位于根命名空间。 - 启动参数中只对一端做了重映射。
- 一端代码使用绝对名称,另一端使用相对名称。
修正后再次执行:
ros2 topic info /counter -v
确认 Publisher count 和 Subscription count 都不为 0。
18. 坑四:Topic 存在,但类型或 QoS 不兼容
18.1 错误表现
ros2 topic list能看到/counter。rqt_graph中节点可能存在,但没有预期的数据流。- Subscriber 没有任何回调日志。
- 命令行可能提示发现了不兼容 QoS。
18.2 详细检查
ros2 topic type /counter
ros2 topic info /counter -v
确认两端都是:
std_msgs/msg/Int32
继续比较各端点的 Reliability、Durability 和其他 QoS 策略。本文基础示例中,两端代码都使用 10,不应出现 QoS 不兼容。如果自行修改了其中一端,应先恢复相同配置验证基础链路。
19. 坑五:命令行正常,但 rqt 看不到内容
19.1 具体场景
以下命令均正常:
ros2 node list
ros2 topic echo /counter --once
但 rqt_graph 没有节点,rqt_topic 没有 /counter,或者 rqt_plot 没有曲线。
19.2 排查步骤
步骤 1:从已经正确加载环境的同一终端启动 rqt。
source /opt/ros/humble/setup.bash
source ~/topic_ws/install/setup.bash
rqt
步骤 2:刷新插件视图,并检查是否启用了过滤条件。
步骤 3:确认 rqt 与节点使用相同 Domain ID。
printenv ROS_DOMAIN_ID
步骤 4:重启 ROS 2 daemon 后重新打开工具。
ros2 daemon stop
ros2 daemon start
步骤 5:确认插件已经安装。
ros2 pkg prefix rqt_graph
ros2 pkg prefix rqt_topic
ros2 pkg prefix rqt_plot
步骤 6:检查图形环境。通过 SSH 连接远程设备时,需要可用的 X11 转发、远程桌面或在本机运行 rqt。容器中运行时,还需要正确转发显示服务。
步骤 7:对于 rqt_plot,确认输入的是数值字段路径:
/counter/data
只输入 /counter 通常无法直接绘制 Int32 消息对象。
20. 一套实用的分层排障方法
第 1 层:环境
printenv ROS_DISTRO
printenv ROS_DOMAIN_ID
which ros2
which rqt
目标:确认命令、发行版和通信域一致。
第 2 层:软件包与入口
cd ~/topic_ws
colcon list
ros2 pkg prefix topic_demo
ros2 pkg executables topic_demo
目标:确认软件包已构建并且两个入口可被发现。
第 3 层:节点
ros2 node list
ros2 node info /counter_publisher
ros2 node info /counter_subscriber
目标:确认进程仍在运行,节点已加入 ROS 图,并创建了预期端点。
第 4 层:Topic 接口
ros2 topic list -t
ros2 topic type /counter
ros2 topic info /counter -v
目标:确认名称、类型、端点和 QoS 正确。
第 5 层:消息数据
ros2 topic echo /counter --once
ros2 topic hz /counter
目标:确认消息实际到达,频率符合定时器设置。
第 6 层:可视化
rqt_graph
rqt_topic
rqt_plot
目标:确认拓扑、实时字段和趋势曲线与命令行结果一致。
排障时应按层级推进。如果第 4 层显示没有 Publisher,就没有必要先修改 rqt_plot;如果命令行已经能持续收到数据,优先检查 rqt 环境和字段路径,而不是重写节点逻辑。
21. 标准构建、运行与验证流程
步骤 1:加载系统环境
source /opt/ros/humble/setup.bash
步骤 2:检查源代码
cd ~/topic_ws
python3 -m py_compile \
src/topic_demo/topic_demo/counter_publisher.py \
src/topic_demo/topic_demo/counter_subscriber.py
步骤 3:安装依赖
rosdep install \
--from-paths src \
--ignore-src \
-r \
-y
步骤 4:构建软件包
colcon build \
--symlink-install \
--packages-select topic_demo
步骤 5:加载工作空间
source install/setup.bash
步骤 6:检查入口
ros2 pkg executables topic_demo
步骤 7:启动发布和订阅节点
终端 A:
ros2 run topic_demo counter_publisher
终端 B:
ros2 run topic_demo counter_subscriber
步骤 8:命令行验证
终端 C:
ros2 node list
ros2 topic info /counter -v
ros2 topic echo /counter --once
ros2 topic hz /counter
步骤 9:rqt 验证
终端 D:
rqt_graph
再分别启动 rqt_topic 和 rqt_plot,确认拓扑、实时数据和 /counter/data 曲线。
22. 常见错误信息速查
| 错误或现象 | 常见原因 | 优先处理 |
|---|---|---|
ros2: command not found |
未加载系统环境 | source /opt/ros/<发行版>/setup.bash |
Package 'topic_demo' not found |
未构建或未加载工作空间 | 构建后 source install/setup.bash |
No executable found |
console_scripts 未注册或未重建 |
检查 setup.py 并重新构建 |
| 发布有日志,订阅无日志 | 名称、类型或 QoS 不匹配 | ros2 topic info /counter -v |
Publisher count: 0 |
发布节点未运行或名称不同 | node list、node info |
Subscription count: 0 |
订阅节点未运行或名称不同 | node list、node info |
topic echo 一直等待 |
当前没有消息到达 | 检查发布回调和频率 |
rqt_graph 没有目标节点 |
环境不同、过滤或未刷新 | 同环境启动并刷新 |
rqt_topic 有 Topic 但值不更新 |
没有勾选订阅或发布已停止 | 勾选 Topic 并检查 topic hz |
rqt_plot 没有曲线 |
字段路径错误或字段非数值 | 使用 /counter/data |
| 两台设备互相看不到 | Domain ID、网络、防火墙或 RMW 问题 | 先完成同机测试,再检查 DDS 网络 |
23. 建议保留的最小检查清单
每次编写 Topic 发布订阅程序时检查:
[ ] 每个终端都加载了正确的 ROS 2 环境
[ ] 工作空间已构建并加载 install/setup.bash
[ ] Publisher 和 Subscriber 入口都能被 ros2 pkg executables 找到
[ ] 两个节点都能在 ros2 node list 中看到
[ ] 最终 Topic 名称完全一致
[ ] 消息类型完全一致
[ ] QoS 配置兼容
[ ] ros2 topic info 显示发布和订阅端点
[ ] ros2 topic echo 能收到消息
[ ] ros2 topic hz 与程序周期基本一致
[ ] rqt_graph 中连接方向正确
[ ] rqt_topic 中字段实时更新
[ ] rqt_plot 使用了正确的数值字段路径
遇到故障时,一次只改变一个因素。例如先统一 Topic 名称并验证,再调整 QoS;不要同时修改包名、节点名、命名空间、消息类型和 Domain ID,否则很难判断真正生效的是哪一项。
24. 总结与建议
Topic 发布订阅的核心不是让两个 Python 文件同时输出日志,而是建立一条可以被逐层证明的数据链路:
- Publisher 创建并发布指定类型的消息。
- Topic 使用最终解析后的名称连接通信端点。
- Subscriber 以兼容的类型和 QoS 接收消息。
- ROS 2 命令行验证节点、端点、内容和频率。
- rqt 从拓扑、实时字段和趋势曲线三个角度验证运行状态。
最实用的验证顺序是:先命令行,后图形界面;先确认节点存在,再检查 Topic;先确认端点匹配,再查看消息内容;最后使用 rqt_graph、rqt_topic 和 rqt_plot 验证整体行为。
当 /counter_publisher 和 /counter_subscriber 能够在 ROS 图中互相连接,ros2 topic echo /counter 能持续收到递增整数,ros2 topic hz /counter 接近 2 Hz,并且 rqt_plot 中 /counter/data 曲线持续上升时,才算真正完成了从代码到可视化的 Topic 发布订阅闭环。




