ROS2 Python + Qt 状态显示界面完整配置
本篇目标:在 ROS2 中创建一个 Python 功能包,用 Qt 做图形界面,并通过订阅 ROS2 Topic 实时更新界面文字。
推荐技术组合:
- ROS2 Python 客户端:
rclpy - Qt Python 绑定:
PySide6 - 消息类型:
std_msgs/msg/String - 运行方式:
ros2 run status_display status_display
一、为什么不能直接照搬 C++ Qt 写法
原始 C++ 示例大概是:
#include <QApplication>
#include <QWidget>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QLabel* label = new QLabel("");
QString message = QString::fromStdString("Hello Qt!");
label->setText(message);
label->show();
app.exec();
return 0;
}
注意:QString::fromStdString 不能写成 QStubg::fromStdString。
如果走 Python + ROS2,就不应该创建 rclcpp 包,而应该创建 ament_python 包,并使用 rclpy。
二、创建 ROS2 Python 功能包
在工作空间的 src 目录下执行:
cd ~/chapt3_ws/topic_practice_ws/src
ros2 pkg create status_display --build-type ament_python --dependencies rclpy std_msgs --license Apache-2.0
创建后目录大致如下:
status_display/
├── package.xml
├── setup.py
├── setup.cfg
├── resource/
│ └── status_display
├── status_display/
│ └── __init__.py
└── test/
三、安装 Qt Python 依赖
推荐使用 PySide6:
pip3 install PySide6
如果下载非常慢,说明正在从默认 PyPI 源拉取较大的 wheel 包。可以临时使用国内镜像:
pip3 install PySide6 -i https://mirrors.aliyun.com/pypi/simple/
如果在 Ubuntu / WSL2 中安装较慢,可以先确认 pip 可用:
python3 -m pip --version
如果缺少 pip:
sudo apt update
sudo apt install python3-pip -y
本文统一使用 Qt6,也就是 PySide6。后面的所有代码都按 PySide6 编写。
四、编写 Qt + ROS2 节点
新建文件:
touch ~/chapt3_ws/topic_practice_ws/src/status_display/status_display/status_display_node.py
写入以下代码:
import sys
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
from PySide6.QtCore import QTimer
from PySide6.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget
class StatusDisplayWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('ROS2 状态显示器')
self.resize(420, 140)
self.status_label = QLabel('等待 /status 消息...')
self.status_label.setStyleSheet('font-size: 24px; padding: 20px;')
layout = QVBoxLayout()
layout.addWidget(self.status_label)
self.setLayout(layout)
def update_status(self, text: str):
self.status_label.setText(text)
class StatusDisplayNode(Node):
def __init__(self, window: StatusDisplayWindow):
super().__init__('status_display')
self.window = window
self.subscription = self.create_subscription(
String,
'status',
self.status_callback,
10
)
self.get_logger().info('status_display 节点已启动,正在订阅 /status')
def status_callback(self, msg: String):
self.window.update_status(msg.data)
self.get_logger().info(f'界面已更新:{msg.data}')
def main():
rclpy.init()
app = QApplication(sys.argv)
window = StatusDisplayWindow()
window.show()
node = StatusDisplayNode(window)
ros_timer = QTimer()
ros_timer.timeout.connect(lambda: rclpy.spin_once(node, timeout_sec=0))
ros_timer.start(10)
exit_code = app.exec()
node.destroy_node()
rclpy.shutdown()
sys.exit(exit_code)
if __name__ == '__main__':
main()
五、为什么要用 QTimer + rclpy.spin_once
Qt 有自己的事件循环:
app.exec()
ROS2 也有自己的事件循环:
rclpy.spin(node)
这两个函数都会阻塞。如果直接这样写:
app.exec()
rclpy.spin(node)
后面的 ROS2 永远没机会运行。
如果这样写:
rclpy.spin(node)
app.exec()
后面的 Qt 窗口也永远没机会运行。
所以在 Qt 程序里,更适合使用:
rclpy.spin_once(node, timeout_sec=0)
再用 Qt 的 QTimer 每隔几毫秒调用一次它:
ros_timer = QTimer()
ros_timer.timeout.connect(lambda: rclpy.spin_once(node, timeout_sec=0))
ros_timer.start(10)
这样 Qt 界面保持响应,ROS2 消息也能被及时处理。
六、配置 setup.py
打开:
~/chapt3_ws/topic_practice_ws/src/status_display/setup.py
确保 entry_points 中有这个入口:
entry_points={
'console_scripts': [
'status_display = status_display.status_display_node:main',
],
},
七、配置 package.xml
打开:
~/chapt3_ws/topic_practice_ws/src/status_display/package.xml
确认包含:
<depend>rclpy</depend>
<depend>std_msgs</depend>
PySide6 通常通过 pip 安装,不一定写进 package.xml。如果你希望在文档上标明,也可以加:
<exec_depend>python3-pyside6</exec_depend>
八、编译功能包
回到工作空间根目录:
cd ~/chapt3_ws/topic_practice_ws
colcon build
source install/setup.bash
如果只想编译这一个包:
colcon build --packages-select status_display
source install/setup.bash
九、运行 Qt 状态显示器
启动界面节点:
ros2 run status_display status_display
此时会弹出一个 Qt 窗口,默认显示:
等待 /status 消息...
十、发布测试消息
另开一个终端,记得先 source:
cd ~/chapt3_ws/topic_practice_ws
source install/setup.bash
发布一条测试消息:
ros2 topic pub /status std_msgs/msg/String "{data: '机器人状态:正常运行'}" --once
如果一切正常,Qt 窗口里的文字会变成:
机器人状态:正常运行
持续发布:
ros2 topic pub /status std_msgs/msg/String "{data: '电量 80%,导航中'}" -r 1
-r 1 表示每秒发布 1 次。
十一、调试命令
查看话题是否存在:
ros2 topic list
查看 /status 消息内容:
ros2 topic echo /status
查看节点信息:
ros2 node info /status_display
查看消息类型:
ros2 interface show std_msgs/msg/String
十二、常见问题
12.1 运行时报 ModuleNotFoundError: No module named 'PySide6'
说明当前 Python 环境没有安装 PySide6:
pip3 install PySide6
如果你用了虚拟环境,要确保 ros2 run 用的是同一个 Python 环境。
12.2 窗口弹不出来
如果是在 WSL2 中运行 Qt,需要确认 Windows 侧支持 GUI 显示。Windows 11 的 WSLg 通常可以直接显示。
可以先测试:
python3 -c "from PySide6.QtWidgets import QApplication, QLabel; import sys; app=QApplication(sys.argv); w=QLabel('Qt OK'); w.show(); app.exec()"
如果这个测试都无法弹窗,问题不在 ROS2,而在 WSL GUI / Qt 环境。
12.3 ros2 run 找不到 status_display
通常是以下原因:
- 忘记执行
colcon build。 - 忘记执行
source install/setup.bash。 setup.py的console_scripts写错。- Python 文件名或
main函数名写错。
12.4 收不到 /status 消息
按顺序检查:
ros2 topic list
ros2 topic echo /status
ros2 node info /status_display
如果 ros2 topic echo /status 有数据,但界面不更新,重点检查:
- 订阅的话题名是不是
status - 消息类型是不是
std_msgs/msg/String QTimer有没有调用rclpy.spin_once
十三、最小运行流程汇总
cd ~/chapt3_ws/topic_practice_ws/src
ros2 pkg create status_display --build-type ament_python --dependencies rclpy std_msgs --license Apache-2.0
pip3 install PySide6
# 编写 status_display/status_display/status_display_node.py
# 修改 setup.py 的 console_scripts
cd ~/chapt3_ws/topic_practice_ws
colcon build --packages-select status_display
source install/setup.bash
ros2 run status_display status_display
另开终端:
cd ~/chapt3_ws/topic_practice_ws
source install/setup.bash
ros2 topic pub /status std_msgs/msg/String "{data: 'Hello Qt + ROS2 Python'}" --once
十四、显示前面章节的 CPU 使用信息
前面章节已经定义了自定义消息:
status_interfaces/msg/SystemStatus
发布者发布的话题是:
/system_status
消息字段包括:
builtin_interfaces/Time stamp
string host_name
float32 cpu_percent
float32 memory_percent
float32 memory_total
float32 memory_available
float64 net_sent
float64 net_recv
所以 Qt 显示端不应该再订阅 std_msgs/msg/String,而应该订阅:
from status_interfaces.msg import SystemStatus
14.1 安装 Qt 依赖
本章节继续使用 Qt6,也就是 PySide6。如果还没有安装,推荐先使用国内镜像安装:
pip3 install PySide6 -i https://mirrors.aliyun.com/pypi/simple/
如果想长期使用清华源,也可以先配置 pip:
pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip3 install PySide6
如果 Qt 界面里的中文显示成方框,说明系统缺少中文字体。WSL / Ubuntu 里安装中文字体:
sudo apt update
sudo apt install fonts-noto-cjk -y
安装后关闭当前 Qt 窗口,重新运行节点。
14.2 创建或修改 status_display 包
如果还没创建显示包:
cd ~/chapt3_ws/topic_practice_ws/src
ros2 pkg create status_display --build-type ament_python --dependencies rclpy status_interfaces --license Apache-2.0
如果已经创建过 status_display,就不用重复创建,只需要确认 package.xml 里有:
<depend>rclpy</depend>
<depend>status_interfaces</depend>
如果希望在依赖说明里标记 Qt6 运行依赖,也可以加:
<exec_depend>python3-pyside6</exec_depend>
不过 PySide6 通常通过 pip3 install PySide6 安装,实际教学中不写这一项也可以。
14.3 编写 CPU 状态 Qt 显示节点
新建文件:
touch ~/chapt3_ws/topic_practice_ws/src/status_display/status_display/cpu_status_display.py
写入:
import sys
import rclpy
from rclpy.node import Node
from status_interfaces.msg import SystemStatus
from PySide6.QtCore import QTimer
from PySide6.QtGui import QFont
from PySide6.QtWidgets import (
QApplication,
QLabel,
QProgressBar,
QVBoxLayout,
QWidget,
)
class CpuStatusWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('ROS2 系统状态监控')
self.resize(920, 520)
self.setStyleSheet("""
QWidget {
font-size: 24px;
}
QLabel {
padding: 8px;
}
QProgressBar {
min-height: 36px;
text-align: center;
font-size: 22px;
}
""")
self.host_label = QLabel('主机名:等待数据...')
self.cpu_label = QLabel('CPU:等待数据...')
self.memory_label = QLabel('内存:等待数据...')
self.net_label = QLabel('网络:等待数据...')
self.cpu_bar = QProgressBar()
self.cpu_bar.setRange(0, 1000)
self.cpu_bar.setValue(0)
self.memory_bar = QProgressBar()
self.memory_bar.setRange(0, 1000)
self.memory_bar.setValue(0)
layout = QVBoxLayout()
layout.addWidget(self.host_label)
layout.addWidget(self.cpu_label)
layout.addWidget(self.cpu_bar)
layout.addWidget(self.memory_label)
layout.addWidget(self.memory_bar)
layout.addWidget(self.net_label)
self.setLayout(layout)
def update_status(self, msg: SystemStatus):
self.host_label.setText(f'主机名:{msg.host_name}')
cpu_percent = max(0.0, min(100.0, float(msg.cpu_percent)))
memory_percent = max(0.0, min(100.0, float(msg.memory_percent)))
cpu_value = int(cpu_percent * 10)
memory_value = int(memory_percent * 10)
self.cpu_label.setText(f'CPU 使用率:{cpu_percent:.1f}%')
self.cpu_bar.setValue(cpu_value)
self.cpu_bar.setFormat(f'{cpu_percent:.1f}%')
memory_total_gb = msg.memory_total / 1024
memory_available_gb = msg.memory_available / 1024
self.memory_label.setText(
f'内存使用率:{memory_percent:.1f}% '
f'可用:{memory_available_gb:.2f} GB / 总量:{memory_total_gb:.2f} GB'
)
self.memory_bar.setValue(memory_value)
self.memory_bar.setFormat(f'{memory_percent:.1f}%')
self.net_label.setText(
f'网络:发送 {msg.net_sent:.2f} MB,接收 {msg.net_recv:.2f} MB'
)
class CpuStatusDisplayNode(Node):
def __init__(self, window: CpuStatusWindow):
super().__init__('cpu_status_display')
self.window = window
self.subscription = self.create_subscription(
SystemStatus,
'system_status',
self.status_callback,
10
)
self.get_logger().info('CPU 状态显示节点已启动,正在订阅 /system_status')
def status_callback(self, msg: SystemStatus):
self.window.update_status(msg)
self.get_logger().info(
f'收到系统状态:CPU {msg.cpu_percent:.1f}%, '
f'内存 {msg.memory_percent:.1f}%'
)
def main():
rclpy.init()
app = QApplication(sys.argv)
app.setFont(QFont('Noto Sans CJK SC', 18))
window = CpuStatusWindow()
window.show()
node = CpuStatusDisplayNode(window)
ros_timer = QTimer()
def spin_ros_once():
if rclpy.ok():
rclpy.spin_once(node, timeout_sec=0)
ros_timer.timeout.connect(spin_ros_once)
ros_timer.start(10)
exit_code = app.exec()
ros_timer.stop()
node.destroy_node()
if rclpy.ok():
rclpy.shutdown()
sys.exit(exit_code)
if __name__ == '__main__':
main()
注意:Qt6 / PySide6 使用 app.exec()。
14.4 配置 setup.py
打开:
~/chapt3_ws/topic_practice_ws/src/status_display/setup.py
把入口加入 console_scripts:
entry_points={
'console_scripts': [
'cpu_status_display = status_display.cpu_status_display:main',
],
},
如果里面已经有其他入口,就写成:
entry_points={
'console_scripts': [
'status_display = status_display.status_display_node:main',
'cpu_status_display = status_display.cpu_status_display:main',
],
},
14.5 编译
回到工作空间根目录:
cd ~/chapt3_ws/topic_practice_ws
colcon build
source install/setup.bash
如果之前只改了 status_display:
colcon build --packages-select status_display
source install/setup.bash
如果提示找不到 status_interfaces,先确认接口包已经编译过:
colcon build --packages-select status_interfaces
source install/setup.bash
然后再编译显示包。
14.6 运行顺序
终端 1:启动 CPU 信息发布者。
cd ~/chapt3_ws/topic_practice_ws
source install/setup.bash
ros2 run status_publisher sys_status_pub
终端 2:启动 Qt 显示界面。
cd ~/chapt3_ws/topic_practice_ws
source install/setup.bash
ros2 run status_display cpu_status_display
如果正常,Qt 窗口会显示:
- 主机名
- CPU 使用率
- 内存使用率
- 可用内存 / 总内存
- 网络发送 / 接收数据量
14.7 调试命令
查看系统状态话题是否存在:
ros2 topic list
查看发布者是否真的在发数据:
ros2 topic echo /system_status
查看自定义消息格式:
ros2 interface show status_interfaces/msg/SystemStatus
查看 Qt 显示节点:
ros2 node info /cpu_status_display
如果 ros2 topic echo /system_status 有数据,但 Qt 不更新,重点检查:
status_display/package.xml是否依赖了status_interfaces。- Python 文件里是否导入了
from status_interfaces.msg import SystemStatus。 - 订阅话题名是否写成了
system_status。 setup.py的入口是否写成了cpu_status_display = status_display.cpu_status_display:main。- 修改后是否重新
colcon build并source install/setup.bash。
更多推荐


所有评论(0)