
- Container:容器相关
- Actions:动作相关, 比如查看动作类型
- Action Type Browser:查看 action 对应的消息类型
- Configuration:配置相关
- Parameter Reconfigure:配置动态参数
- Intorspection:节点相关,
- Node Graph:查看节点图
- Logging:日志相关
- bag:数据录制与回放
- Console:查看日志
- Miscellaneours Tools:杂项
- Python console
- Shell
- Services:服务相关
- Service Caller:调用服务
- Service Type Browser:查看 service 对应的消息类型
- Topics:话题相关
- Message Publisher:发布话题
- Messga Type Browser:查看 action 对应的消息类型
- Topic Monitor:查看当前可用的 topic 消息
- Visualization 视觉相关
- Image View:查看图像
- Plot:绘制 topic 曲线图
rqt 框架架构详解
概述
rqt 是 ROS 2 的一个可视化框架,用于构建图形化工具(GUI plugins)。它采用插件化架构,允许用户通过安装不同的 rqt 插件来扩展功能(如 rqt_graph、rqt_topic、rqt_msg 等)。
rqt 的核心设计思想:
- 分层架构:从底层 Qt 框架逐层向上,最终支持 Python 和 C++ 插件。
- 插件发现与加载:通过包管理系统自动发现和动态加载插件。
- ROS 集成:通过 rclpy/roscpp 将 ROS 功能集成到 GUI 中。
- 线程隔离:使用独立的 spinner 线程驱动 ROS 事件循环,避免阻塞 GUI 主线程。
@startuml
!theme plain
skinparam backgroundColor #FEFEFE
skinparam classBackgroundColor #F0F0F0
skinparam classBorderColor #333333
skinparam arrowColor #333333
title rqt 框架架构图 (ROS 2 Humble)
' ============== 顶层包定义 ==============
package "qt_gui (Qt 框架基础层)" {
interface "PluginProvider" as IPluginProvider {
+discover()
+load()
+unload()
}
interface "PluginContext" as IPluginContext {
+serial_number()
}
interface "Plugin" as IPlugin {
}
class "CompositePluginProvider" as CompositePluginProvider {
-plugin_providers
+discover()
+load()
+unload()
}
class "Main" as QtMain {
+main()
+create_application()
}
}
package "python_qt_binding (Qt Python 绑定)" {
class "QWidget" as QWidget
class "QThread" as QThread
class "QCompleter" as QCompleter
class "QTreeView" as QTreeView
}
package "rclpy (ROS 2 Python 客户端库)" {
class "Node" as RclpyNode {
+create_node()
+destroy_node()
+get_node_names_and_namespaces()
}
class "MultiThreadedExecutor" as MultiThreadedExecutor {
+add_node()
+spin_once()
}
}
' ============== rqt_gui 层 ==============
package "rqt_gui (rqt GUI 框架主层)" {
class "Main" as RqtMain {
-plugin_providers
+_add_plugin_providers()
+_add_reload_paths()
}
class "RosPluginProvider" as RosPluginProvider {
#_export_tag
#_base_class_type
#_plugin_descriptors
+discover()
+load()
+_find_plugins()
+_parse_plugin_xml()
}
class "RospkgPluginProvider" as RospkgPluginProvider {
+_find_plugins()
}
class "Ros2PluginContext" as Ros2PluginContext {
+node: rclpy.Node
}
}
' ============== rqt_gui_py 层 ==============
package "rqt_gui_py (rqt Python 插件支持)" {
class "Plugin" as RqtPlugin {
+shutdown_plugin()
}
class "RosPyPluginProvider" as RosPyPluginProvider {
-_node: rclpy.Node
-_spinner: RclpySpinner
-_node_initialized: bool
+load()
+unload()
+_init_node()
+_destroy_node()
}
class "RclpySpinner" as RclpySpinner {
-_node: rclpy.Node
-_abort: bool
+run()
+quit()
}
}
' ============== rqt_py_common 工具层 ==============
package "rqt_py_common (rqt Python 工具库)" {
class "RqtRoscommUtil" as RqtRoscommUtil {
+get_node_names()
+get_publisher_names()
+get_subscriber_names()
}
class "MessageTreeWidget" as MessageTreeWidget {
+setModel()
}
class "TreeModelCompleter" as TreeModelCompleter {
+setModel()
}
class "TopicCompleter" as TopicCompleter {
+updateModel()
}
class "PluginContainerWidget" as PluginContainerWidget {
+QWidget child
}
class "MessageTreeModel" as MessageTreeModel {
+expandAll()
+collapseAll()
}
class "TopicTreeModel" as TopicTreeModel {
}
class "TopicDict" as TopicDict {
+get()
}
class "MessageHelpers" as MessageHelpers {
+get_message_class()
}
class "TopicHelpers" as TopicHelpers {
+get_topic_type()
}
class "IniHelper" as IniHelper {
+write()
+read()
}
}
' ============== rqt_gui_cpp 层(可选) ==============
package "rqt_gui_cpp (rqt C++ 插件支持,可选)" {
class "NodeletPluginProvider" as NodeletPluginProvider {
+discover()
+load()
}
class "RoscppPluginProvider" as RoscppPluginProvider {
+discover()
+load()
}
}
' ============== rqt meta 包 ==============
package "rqt (meta 包,依赖整合)" {
note "依赖以上所有包,提供统一的安装与启动入口" as RqtMetaNote
}
' ============== 具体插件示例(虚拟) ==============
package "具体插件示例 (rqt_topic, rqt_msg 等)" {
class "TopicPlugin" as TopicPlugin
class "MessagePlugin" as MessagePlugin
}
' ============== 依赖和继承关系 ==============
' qt_gui 基础层关系
IPluginProvider <|-- CompositePluginProvider
IPluginProvider <|-- RosPluginProvider
IPluginContext <|-- Ros2PluginContext
IPlugin <|-- RqtPlugin
QtMain --> CompositePluginProvider : uses
' rqt_gui 继承关系
RosPluginProvider <|-- RospkgPluginProvider
RqtMain --|> QtMain : extends
RqtMain --> RosPluginProvider : aggregates
RqtMain --> RospkgPluginProvider : registers
' rqt_gui_py 与 rqt_gui 的关系
CompositePluginProvider <|-- RosPyPluginProvider
RosPyPluginProvider --> IPluginProvider
RosPyPluginProvider --> Ros2PluginContext : creates
RosPyPluginProvider --> RospkgPluginProvider : uses
RosPyPluginProvider --> RclpySpinner : creates
' rclpy 集成
Ros2PluginContext --> RclpyNode : holds
RosPyPluginProvider --> RclpyNode : creates
RclpySpinner --> RclpyNode : spins
RclpySpinner --|> QThread : extends
RclpySpinner --> MultiThreadedExecutor : uses
' Qt 集成
RqtPlugin --|> IPlugin : implements
RqtPlugin --> Ros2PluginContext : uses
MessageTreeWidget --|> QTreeView : extends
TreeModelCompleter --|> QCompleter : extends
PluginContainerWidget --|> QWidget : extends
' rqt_py_common 工具与 rqt_gui_py 的关系
RosPyPluginProvider --> RqtRoscommUtil : queries via
RqtRoscommUtil --> RclpyNode : queries
' 消息处理工具链
TopicCompleter --|> TreeModelCompleter : extends
TopicCompleter --> MessageHelpers : uses
MessageHelpers --> TopicHelpers : works with
MessageTreeWidget --> MessageTreeModel : uses
MessageTreeModel --> TopicTreeModel : may contain
TopicDict --> MessageTreeModel : provides data
IniHelper --> PluginContainerWidget : persists state
' 具体插件与框架的关系
TopicPlugin --|> RqtPlugin : extends
MessagePlugin --|> RqtPlugin : extends
TopicPlugin --> RqtRoscommUtil : uses
TopicPlugin --> PluginContainerWidget : inherits UI
MessagePlugin --> MessageTreeWidget : uses
MessagePlugin --> TopicCompleter : uses
' C++ 插件支持(可选)
NodeletPluginProvider --|> IPluginProvider
RoscppPluginProvider --|> IPluginProvider
note right of RqtMain
应用程序入口
初始化插件提供者
注册 Python/C++ 插件
end note
note right of RosPyPluginProvider
Python 插件生命周期管理
ROS node 创建与销毁
后台 spinner 线程管理
end note
note right of RclpySpinner
在独立 Qt 线程中运行
以多线程 executor 驱动 rclpy node
支持 GUI 与 ROS 并发处理
end note
note right of RqtRoscommUtil
提供图查询接口
查询节点、话题、服务等
通过 rclpy Node 访问
end note
note bottom of "rqt_py_common (rqt Python 工具库)"
通用工具与 UI 组件
支持 topic\/message\/parameter 查询
提供自动补全、树形显示等功能
end note
@enduml 各模块详解
1. qt_gui (Qt 框架基础层)
来源:独立的 Qt GUI 框架包(非 ROS 特定)
核心接口:
PluginProvider(虚基类):负责插件的发现、加载、卸载。PluginContext:传递给每个插件,提供与框架交互的能力。Plugin:插件要实现的基类。CompositePluginProvider:可管理多个子PluginProvider的复合提供者。Main:应用程序入口点,管理主窗口、菜单、工具栏等。
职责:提供通用的 GUI 框架与插件系统,与 ROS 无关。
2. python_qt_binding (Qt Python 绑定层)
来源:ROS 官方 Qt Python 绑定包
提供:
QWidget、QThread、QCompleter、QTreeView等 Qt 类的 Python 包装。- 支持在运行时选择 PyQt 5 或 PySide 2(通过环境变量
QT_BINDING)。
职责:统一 Qt 与 Python 的接口,隐藏具体 PyQt/PySide 差异。
3. rclpy (ROS 2 Python 客户端库)
来源:ROS 2 官方 Python 客户端库
核心类:
Node:ROS 节点对象。方法包括:get_node_names_and_namespaces():查询 ROS 图中的所有节点。get_publisher_names_and_types_by_node(node_name, namespace):查询节点的 publisher。get_subscriber_names_and_types_by_node(...)等。create_subscription(),create_publisher(),create_client(),create_service()等。
MultiThreadedExecutor:多线程执行器,驱动 node 的回调。add_node(node):注册 node。spin_once(timeout_sec):执行一次迭代(处理回调)。
职责:提供 ROS 2 的 Python 客户端 API,供 rqt 查询 ROS 图和访问 ROS 功能。
4. rqt_gui (rqt GUI 框架主层)
位置:rqt_gui/ 目录
核心类:
4.1 Main 类
class Main(Base): # Base = qt_gui.main.Main
def main(argv, standalone, plugin_argument_provider, help_text):
# 初始化 Qt 应用
# 注册插件提供者(Python、C++)
# 启动主窗口 职责:
- 继承
qt_gui.main.Main,扩展 rqt 特定的初始化逻辑。 - 通过
_add_plugin_providers()注册 Python 和 C++ 插件提供者。 - 调用
rclpy.utilities.remove_ros_args()过滤掉 ROS 特有的命令行参数(如__ns,__node)。 - 设置应用图标、帮助文本等。
启动命令示例:
ros2 run rqt_gui rqt_gui 4.2 RosPluginProvider 类 (抽象)
class RosPluginProvider(PluginProvider):
def discover(discovery_data):
# 查找 plugin.xml 文件
# 解析 XML 提取插件信息(class name, type, icon等)
# 返回 PluginDescriptor 列表
def load(plugin_id, plugin_context):
# 动态导入插件模块
# 实例化插件类
# 传入 plugin_context 初始化 职责:
- 基于 ROS 包系统(ament/rospkg)自动发现插件。
- 解析
plugin.xml清单文件,提取插件元数据(名称、类型、图标等)。 - 动态加载和卸载插件。
plugin. xml 示例:
<library path="lib/python_builtin_plugins">
<class name="Topic Publisher" type="rqt_publisher::Publisher" base_class_type="rqt_gui_py::Plugin">
<description>Publishes ROS topics</description>
<qtgui>
<icon type="theme">folder</icon>
<label>Topic Publisher</label>
</qtgui>
</class>
</library> 4.3 RospkgPluginProvider 类 (子类)
class RospkgPluginProvider(RosPluginProvider):
def _find_plugins(export_tag, discovery_data):
# 使用 rospkg/ament 查找所有包中的特定 export tag
# 返回 [(package_name, plugin_xml_path), ...] 职责:实现 _find_plugins() 的包查询部分,通过 rospkg 枚举所有 ROS 包并查找它们的 plugin.xml。
4.4 Ros2PluginContext 类
class Ros2PluginContext(PluginContext):
def __init__(handler, node):
super().__init__(handler)
self.node = node # ← 暴露给插件的 rclpy.Node 对象 职责:
- 继承
qt_gui.plugin_context.PluginContext。 - 关键:将 rclpy node 对象传递给插件,使插件能访问 ROS 功能。
5. rqt_gui_py (rqt Python 插件支持层)
位置:rqt_gui_py/ 目录
核心类:
5.1 Plugin 基类
class Plugin(Base): # Base = qt_gui.plugin.Plugin
def __init__(context):
super().__init__(context)
def shutdown_plugin():
"""清理资源:取消订阅、停止定时器等"""
pass 职责:
- 为 Python 插件提供标准接口。
- 提醒用户实现
shutdown_plugin()以正确清理资源。 - 不调用
rclpy.init()(由框架统一管理)。
典型使用:
from rqt_gui_py.plugin import Plugin
class MyTopicPlugin(Plugin):
def __init__(self, context):
super().__init__(context)
self.node = context.node # ← 通过 context 获取 rclpy.Node
# 使用 self.node 创建 publisher/subscriber 等
def shutdown_plugin(self):
# 清理
self.node.destroy_node() 5.2 RosPyPluginProvider 类
class RosPyPluginProvider(CompositePluginProvider):
def __init__():
# 包装 RospkgPluginProvider,寻找 rqt_gui_py::Plugin
# 初始化 _node = None, _spinner = None
def load(plugin_id, plugin_context):
self._init_node() # ← 懒初始化
ros_plugin_context = Ros2PluginContext(handler=..., node=self._node)
# 调用父类加载
return super().load(plugin_id, ros_plugin_context)
def _init_node():
if not self._node_initialized:
name = 'rqt_gui_py_node_%d' % os.getpid()
rclpy.init()
self._node = rclpy.create_node(name)
self._spinner = RclpySpinner(self._node)
self._spinner.start() # ← 启动后台线程
self._node_initialized = True 职责:
- 管理 Python 插件的生命周期。
- 关键:在第一个 Python 插件加载时创建全局 rclpy node(懒初始化)。
- 启动
RclpySpinner后台线程驱动 ROS 事件处理。 - node 名称格式:
rqt_gui_py_node_<PID>。
5.3 RclpySpinner 类 (QThread 子类)
class RclpySpinner(QThread):
def __init__(node):
super().__init__()
self._node = node
self._abort = False
def run():
"""在独立线程中执行"""
executor = MultiThreadedExecutor()
executor.add_node(self._node)
while rclpy.ok() and not self._abort:
executor.spin_once(timeout_sec=1.0)
def quit():
self._abort = True
super().quit() 职责:
- 关键设计:在独立的 Qt 线程中驱动 rclpy node,避免阻塞 GUI 线程。
- 使用
MultiThreadedExecutor支持 node 的多个回调并发执行。 spin_once(timeout_sec=1.0)每次等待最多 1 秒以处理回调。- 支持优雅关闭(
quit()设置_abort标志)。
时序图:
GUI 主线程 RclpySpinner 线程
┌────────────────┐ ┌──────────────────┐
│ Main event loop│ │ run() in QThread │
│ - GUI events │ ├──────────────────┤
│ - User input │◄──────────►│ executor.spin() │
│ ... │ │ - ROS callbacks │
└────────────────┘ │ - Timers │
│ - Subscriptions │
└──────────────────┘ 6. rqt_gui_cpp (rqt C++ 插件支持层,可选)
位置:rqt_gui_cpp/ 目录
核心类:
6.1 NodeletPluginProvider 类
- 用于发现和加载基于 Nodelet 架构的 C++ 插件。
- Nodelet:在同一进程中运行多个轻量级"节点"的 ROS C++ 组件加载框架。
6.2 RoscppPluginProvider 类
- 用于发现和加载基于 roscpp(ROS C++ 客户端库)的 C++ 插件。
职责:为 C++ 插件提供与 Python 等价的插件发现与加载机制(通过 CMake 集成)。
7. rqt_py_common (rqt Python 工具库)
位置:rqt_py_common/src/rqt_py_common/ 目录
这是一个公共工具包,为各种 rqt 插件提供重用代码。
核心模块:
7.1 rqt_roscomm_util.py → RqtRoscommUtil 类
功能:访问 ROS 图信息(通过 rclpy node)。
API 示例:
class RqtRoscommUtil:
@staticmethod
def get_node_names(node):
# 返回图中所有节点名
@staticmethod
def get_publisher_names(node, remote_node_name):
# 返回指定节点的 publisher (name, type) 列表
@staticmethod
def get_subscriber_names(node, remote_node_name):
# 返回指定节点的 subscriber 列表
# 类似的还有 get_service_names, get_action_names 等 使用场景:在插件中查询 ROS 图拓扑。
7.2 message_helpers.py → MessageHelpers 类
功能:ROS 消息类型的反射与加载。
API 示例:
class MessageHelpers:
@staticmethod
def get_message_class(message_type_name):
"""根据字符串名加载消息类
示例:'std_msgs/String' → std_msgs.msg.String
""" 7.3 topic_helpers.py → TopicHelpers 类
功能:话题类型信息查询与字段遍历。
API 示例:
class TopicHelpers:
@staticmethod
def get_topic_type(node, topic_name):
# 返回话题的消息类型名
@staticmethod
def get_type_class(type_name):
# 加载并返回类型的 Python 类 7.4 UI 组件类
MessageTreeWidget:
- 继承
QTreeView。 - 以树形展示 ROS 消息的字段结构。
- 支持展开/折叠字段。
MessageTreeModel:
- 为
MessageTreeWidget提供数据模型。 - 递归构建消息字段树。
TopicCompleter:
- 继承
TreeModelCompleter(而它继承QCompleter)。 - 为输入框提供话题名自动补全。
- 实时查询 ROS 图并更新补全列表。
TreeModelCompleter:
- 基于树形模型的通用补全器。
PluginContainerWidget:
- 继承
QWidget。 - 为插件 UI 提供容器,包含消息/日志显示区域等。
TopicDict、TopicTreeModel**:
- 管理话题信息的缓存与更新。
7.5 ini_helper.py → IniHelper 类
功能:插件配置持久化(INI 文件格式)。
API 示例:
class IniHelper:
@staticmethod
def write(settings, key, value):
# 写入配置项
@staticmethod
def read(settings, key, default=None):
# 读取配置项 使用场景:保存用户在插件中的设置(窗口大小、选中的话题等)。
关键设计模式
1. 插件发现与动态加载(Strategy Pattern)
ROS 包系统
↓
RospkgPluginProvider._find_plugins()
↓
遍历所有包,找 plugin.xml 文件
↓
RosPluginProvider._parse_plugin_xml()
↓
提取 <class type="..."> 信息,生成 PluginDescriptor
↓
用户在 GUI 中点击菜单加载插件
↓
RosPluginProvider.load(plugin_id, context)
↓
__builtin__.__import__() 动态导入模块
↓
实例化插件类,传入 context
↓
插件开始运行 2. ROS 事件循环隔离(Thread Pattern)
GUI 主线程 RclpySpinner 线程
运行 Qt event loop ←→ 运行 ROS executor loop
优势:
- GUI 响应性不受 ROS 回调阻塞
- ROS 回调可以安全地更新 GUI(通过 Qt 信号) 3. 上下文注入(Dependency Injection Pattern)
RosPyPluginProvider.load()
↓
创建 Ros2PluginContext(handler, node=self._node)
↓
传给插件 Plugin.__init__(context)
↓
插件通过 context.node 访问 ROS 功能
↓
避免全局变量,便于测试与多插件隔离 4. 工具库共享(Utility Pattern)
rqt_py_common 提供:
- RqtRoscommUtil:图查询
- MessageHelpers/TopicHelpers:类型反射
- UI 组件:MessageTreeWidget 等
多个具体插件重用这些工具
示例:
rqt_topic, rqt_publisher, rqt_msg 都使用
MessageTreeWidget 来显示消息字段 完整的插件加载流程
用户启动 rqt:
$ ros2 run rqt_gui rqt_gui
1. 进入点:rqt_gui/main.py::Main().main()
↓
2. 继承链:Main(rqt_gui) → Main(qt_gui)
↓
3. qt_gui.main.Main 初始化 Qt 应用、主窗口
↓
4. rqt_gui.Main._add_plugin_providers()
- 注册 RospkgPluginProvider('qt_gui', 'qt_gui_py::Plugin')
- 注册 RospkgPluginProvider('rqt_gui', 'rqt_gui_py::PluginProvider')
- 如果有 C++ 支持,注册 NodeletPluginProvider / RoscppPluginProvider
↓
5. 对每个 PluginProvider.discover()
- 遍历 ROS 包,查找对应 export_tag 的 plugin.xml
- 解析并返回 PluginDescriptor 列表
↓
6. GUI 显示 Plugins 菜单,列出所有发现的插件
↓
7. 用户点击菜单项加载插件 → Main 调用 load(plugin_id, context)
↓
8. 首次加载 Python 插件 → RosPyPluginProvider._init_node()
- rclpy.init()
- self._node = rclpy.create_node('rqt_gui_py_node_<PID>')
- self._spinner = RclpySpinner(self._node)
- self._spinner.start() ← 启动后台线程
↓
9. RosPyPluginProvider.load(plugin_id, plugin_context)
- 创建 Ros2PluginContext(handler, node)
- 调用父类 load,执行 RosPluginProvider 的加载逻辑
↓
10. RosPluginProvider.load()
- 通过 PluginDescriptor 得到模块名、类名
- sys.path.append(library_path)
- module = __builtin__.__import__(module_name, [...])
- class_ref = getattr(module, class_name)
- instance = class_ref(ros_plugin_context) ← 传入 context
- return instance
↓
11. 插件对象现在活跃
- 访问 context.node → 进行 ROS 通信
- 在后台,RclpySpinner 线程驱动 node 的回调执行
↓
12. 用户关闭插件 → 调用 unload(plugin_instance)
- 触发 plugin.shutdown_plugin()
- 插件清理资源
↓
13. 所有插件关闭 → 应用退出
- RosPyPluginProvider.shutdown()
- self._spinner.quit() → 停止线程
- self._node.destroy_node()
- rclpy.try_shutdown() 常见的具体插件示例
rqt_graph
- 显示 ROS 计算图(节点、话题、服务的依赖关系)。
- 使用
rqt_py_common.rqt_ros_graph模块获取图数据。 - 使用 graphviz 或类似库绘制。
rqt_topic
- 订阅并显示 ROS 话题消息。
- 使用
rqt_py_common.message_tree_widget显示消息字段。 - 使用
RqtRoscommUtil列出可用话题。
rqt_publisher
- 发布 ROS 话题。
- 用
rqt_py_common.topic_completer自动补全话题名。 - 用
rqt_py_common.message_tree_widget编辑消息字段。
rqt_msg / rqt_srv / rqt_action
- 浏览 ROS 消息/服务/动作定义。
- 使用消息反射工具加载类型定义。
总结
| 模块 | 职责 | 关键类 |
|---|---|---|
| qt_gui | Qt 框架抽象层 | Main, PluginProvider, PluginContext, Plugin |
| python_qt_binding | Qt 与 Python 绑定 | QWidget, QThread, QCompleter, ... |
| rclpy | ROS 2 Python 客户端 | Node, MultiThreadedExecutor |
| rqt_gui | ROS 框架集成入口 | Main, RosPluginProvider, RospkgPluginProvider, Ros2PluginContext |
| rqt_gui_py | Python 插件运行时 | Plugin, RosPyPluginProvider, RclpySpinner |
| rqt_gui_cpp | C++ 插件运行时(可选) | NodeletPluginProvider, RoscppPluginProvider |
| rqt_py_common | 工具库 | RqtRoscommUtil, MessageHelpers, TopicHelpers, UI 组件 |
核心要点:
- 插件系统采用发现、描述、加载、执行 的标准模式。
- ROS 事件循环通过 RclpySpinner 后台线程 与 Qt GUI 主线程隔离。
- 所有 Python 插件共享一个 全局 rclpy node,由
RosPyPluginProvider管理。 - 插件通过 Ros 2 PluginContext 获得 node 对象,进行 ROS 通信。
- rqt_py_common 提供高级工具库,使编写插件更简便。