【ROS2学习笔记】话题通信篇:话题通信项目实践——系统状态监测与可视化工具
前言
本系列博文是本人的学习笔记,自用为主,不是教程,学习请移步其他大佬的相关教程。主要学习途径为@鱼香ROS大佬的教程,欢迎各位大佬交流学习,若有错误,轻喷。
一、项目背景与准备
需制作系统状态监测与可视化工具,结合 ROS2 话题通信、Python(获取并发布系统信息)、C++/Qt(订阅并界面显示)实现。首先创建工作空间:在 chapt3
目录下执行:
mkdir -p chapt3/topic_practice_ws/src
cd chapt3/topic_practice_ws
colcon build # 初始化工作空间
二、步骤 1:自定义通信接口(消息类型)
ROS2 内置接口无法满足 “系统状态(CPU、内存、网络等)传输” 需求,需自定义消息接口。
1. 创建接口功能包
在 topic_practice_ws/src
目录下执行:
ros2 pkg create status_interfaces --build-type ament_cmake --dependencies rosidl_default_generators builtin_interfaces --license Apache-2.0
--build-type ament_cmake
:指定构建类型(适合 C++ 及接口生成)。--dependencies
:依赖rosidl_default_generators
(将自定义消息转为 C++/Python 代码)、builtin_interfaces
(内置接口,如时间类型)。
2. 编写消息定义文件
在 status_interfaces
功能包下创建 msg
目录,新建 SystemStatus.msg
,内容如下:
builtin_interfaces/Time stamp # 记录时间戳
string host_name # 系统名称
float32 cpu_percent # CPU 使用率
float32 memory_percent # 内存使用率
float32 memory_total # 内存总量
float32 memory_available # 剩余有效内存
float64 net_sent # 网络发送数据总量
float64 net_recv # 网络接收数据总量
- 语法:类似 C++ 变量定义(
类型 名称 # 注释
)。 - 基础类型:ROS2 还支持
bool
、byte
、char
、int8/uint8
等 9 种基础类型。
3. 注册消息文件(修改 CMakeLists.txt
)
在 status_interfaces/CMakeLists.txt
中添加:
# 查找依赖
find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(builtin_interfaces REQUIRED)# 声明消息接口文件,并指定依赖
rosidl_generate_interfaces(${PROJECT_NAME}"msg/SystemStatus.msg"DEPENDENCIES builtin_interfaces
)ament_package()
4. 声明接口包(修改 package.xml
)
在 status_interfaces/package.xml
中添加:
<license>Apache-2.0</license>
<member_of_group>rosidl_interface_packages</member_of_group>
<buildtool_depend>ament_cmake</buildtool_depend>
<member_of_group>
:声明为 “消息接口功能包”,让 ROS2 特殊处理。
5. 构建与验证接口
- 构建:在工作空间根目录执行
colcon build --packages-select status_interfaces
。 - 生效环境:
source install/setup.bash
。 - 验证:执行
ros2 interface show status_interfaces/msg/SystemStatus
,若输出消息结构(时间戳、各字段),则接口创建成功。
三、步骤 2:系统信息获取与发布(Python 节点)
创建 Python 节点,通过 psutil
获取系统 CPU、内存、网络信息,再通过话题发布。
1. 创建发布者功能包
在 topic_practice_ws/src
目录下执行:
ros2 pkg create status_publisher --build-type ament_python --dependencies rclpy status_interfaces --license Apache-2.0
--build-type ament_python
:指定为 Python 类型功能包。--dependencies
:依赖rclpy
(Python 的 ROS2 客户端库)、status_interfaces
(自定义接口)。
2. 编写发布节点代码
在 status_publisher/status_publisher/
下新建 sys_status_pub.py
,代码如下:
import rclpy
from rclpy.node import Node
from status_interfaces.msg import SystemStatus # 导入自定义消息
import psutil # 系统信息获取库
import platformclass SysStatusPub(Node):def __init__(self, node_name):super().__init__(node_name)# 创建发布者:话题名 sys_status,队列大小 10self.status_publisher_ = self.create_publisher(SystemStatus, 'sys_status', 10)# 创建定时器:1 秒调用一次 timer_callbackself.timer = self.create_timer(1, self.timer_callback)def timer_callback(self):# 获取系统信息cpu_percent = psutil.cpu_percent()memory_info = psutil.virtual_memory()net_io_counters = psutil.net_io_counters()# 构造 SystemStatus 消息msg = SystemStatus()msg.stamp = self.get_clock().now().to_msg() # 当前时间转成消息时间戳msg.host_name = platform.node() # 主机名msg.cpu_percent = cpu_percentmsg.memory_percent = memory_info.percentmsg.memory_total = memory_info.total / 1024 / 1024 # 字节转 MBmsg.memory_available = memory_info.available / 1024 / 1024msg.net_sent = net_io_counters.bytes_sent / 1024 / 1024msg.net_recv = net_io_counters.bytes_recv / 1024 / 1024self.get_logger().info(f'发布:{str(msg)}') # 日志输出self.status_publisher_.publish(msg) # 发布消息def main():rclpy.init() # 初始化 rclpynode = SysStatusPub('sys_status_pub')rclpy.spin(node) # 循环运行节点(等待回调)rclpy.shutdown() # 关闭 rclpyif __name__ == '__main__':main()
在 ROS2 的 ament_python
类型包 中,setup.py
的 entry_points
是核心配置:它负责告诉 ROS2:
- 节点命令名(比如
sys_status_pub
,用于ros2 run
调用); - 对应的 Python 文件(比如
status_publisher/sys_status_pub.py
); - 文件中的入口函数(比如
main
函数)。
默认用 ros2 pkg create
生成的 setup.py
中,entry_points
是空的(如下),必须手动补充:
# 默认生成的空entry_points(无法识别节点)
entry_points={'console_scripts': [],
},
3. 运行发布节点
- 构建:在工作空间根目录执行
colcon build --packages-select status_publisher
。 - 生效环境:
source install/setup.bash
。 - 运行:
ros2 run status_publisher sys_status_pub
。 - 验证:新开终端,执行
ros2 topic echo /sys_status
,若看到系统信息持续输出,说明发布成功。
四、步骤 3:Qt 界面显示(C++ 节点)
用 Qt 创建界面,订阅 /sys_status
话题并显示系统状态。
1. 创建显示功能包
在 topic_practice_ws/src
目录下执行:
ros2 pkg create status_display --build-type ament_cmake --dependencies rclcpp status_interfaces --license Apache-2.0
- 依赖
rclcpp
(C++ 的 ROS2 客户端库)、status_interfaces
(自定义接口)。
2. 简单 Qt 界面测试(hello_qt
)
在 status_display/src
下新建 hello_qt.cpp
,代码如下:
#include <QApplication>
#include <QLabel>
#include <QString>int main(int argc, char* argv[]) {QApplication app(argc, argv); // Qt 应用对象QLabel* label = new QLabel(); // 文本标签组件QString message = QString::fromStdString("Hello Qt!"); // 字符串转换label->setText(message); // 设置显示文本label->show(); // 显示组件return app.exec(); // Qt 事件循环(阻塞,处理界面事件)
}
3. 配置 CMakeLists.txt
(hello_qt
)
在 status_display/CMakeLists.txt
中添加:
cmake_minimum_required(VERSION 3.8)project(status_display)find_package(status_interfaces REQUIRED)
find_package(Qt5 REQUIRED COMPONENTS Widgets) # 查找 Qt 的 Widgets 组件# 生成可执行文件 hello_qt
add_executable(hello_qt src/hello_qt.cpp)
# 链接 Qt 的 Widgets 库
target_link_libraries(hello_qt Qt5::Widgets)install(TARGETS hello_qtDESTINATION lib/${PROJECT_NAME})ament_package()
4. 运行 hello_qt
- 构建:
colcon build --packages-select status_display
。 - 生效环境:
source install/setup.bash
。 - 运行:
ros2 run status_display hello_qt
,会看到显示 “Hello Qt!” 的小窗口。
5. 订阅话题并显示系统状态
在 status_display/src
下新建 sys_status_display.cpp
,代码分两部分:
(1)类定义与消息转换
#include <QApplication>
#include <QLabel>
#include <QString>
#include "rclcpp/rclcpp.hpp"
#include "status_interfaces/msg/system_status.hpp" // 自定义消息头文件// 为自定义消息起别名,简化代码
using SystemStatus = status_interfaces::msg::SystemStatus;class SysStatusDisplay : public rclcpp::Node {
public:SysStatusDisplay() : Node("sys_status_display") {// 创建订阅者:话题 sys_status,队列 10,Lambda 回调更新界面subscription_ = this->create_subscription<SystemStatus>("sys_status", 10, [this](const SystemStatus::SharedPtr msg) {label_->setText(get_qstr_from_msg(msg));});// 初始化标签(先显示空消息结构)label_ = new QLabel(get_qstr_from_msg(std::make_shared<SystemStatus>()));label_->show();}// 从 SystemStatus 消息转成 QString(格式化显示)QString get_qstr_from_msg(const SystemStatus::SharedPtr msg) {std::stringstream show_str;show_str << "=============系统状态可视化工具=============\n"<< "数据时间:\t" << msg->stamp.sec << "\t\n"<< "主机名:\t" << msg->host_name << "\t\n"<< "CPU 使用率:\t" << msg->cpu_percent << "%\t\n"<< "内存使用率:\t" << msg->memory_percent << "%\t\n"<< "内存总大小:\t" << msg->memory_total << " MB\t\n"<< "剩余有效内存:\t" << msg->memory_available << " MB\t\n"<< "网络发送量:\t" << msg->net_sent << " MB\t\n"<< "网络接收量:\t" << msg->net_recv << " MB\t\n"<< "============================================";return QString::fromStdString(show_str.str());}private:rclcpp::Subscription<SystemStatus>::SharedPtr subscription_; // 订阅者对象QLabel* label_; // 标签组件
};
(2)主函数(处理 ROS2 与 Qt 事件循环)
int main(int argc, char* argv[]) {rclcpp::init(argc, argv); // ROS2 初始化QApplication app(argc, argv); // Qt 应用初始化// 创建显示节点auto node = std::make_shared<SysStatusDisplay>();// 多线程处理:ROS2 的 spin 放单独线程,避免阻塞 Qt 事件循环std::thread spin_thread([node]() { rclcpp::spin(node); });spin_thread.detach(); // 后台运行线程int result = app.exec(); // Qt 事件循环(处理界面更新)rclcpp::shutdown(); // 关闭 ROS2return result;
}
6. 配置 CMakeLists.txt
(sys_status_display
)
在 status_display/CMakeLists.txt
中添加:
# 生成 sys_status_display 可执行文件
add_executable(sys_status_display src/sys_status_display.cpp)# 链接 Qt Widgets 库
target_link_libraries(sys_status_display Qt5::Widgets)# 为 ROS2 节点添加依赖
ament_target_dependencies(sys_status_display rclcpp status_interfaces)# 安装可执行文件
install(TARGETS hello_qtsys_status_displayDESTINATION lib/${PROJECT_NAME})
7. 运行显示节点
- 构建:
colcon build --packages-select status_display
。 - 生效环境:
source install/setup.bash
。 - 启动流程:
- 先启动发布节点:
ros2 run status_publisher sys_status_pub
。 - 再启动显示节点:
ros2 run status_display sys_status_display
,界面会实时显示系统状态数据。
- 先启动发布节点:
五、整体流程回顾
- 自定义接口:创建
status_interfaces
包,定义SystemStatus
消息,生成跨语言接口代码。 - 发布系统信息:创建
status_publisher
包(Python),用psutil
采集信息,通过/sys_status
话题发布。 - 订阅并显示:创建
status_display
包(C++/Qt),订阅/sys_status
话题,用 Qt 界面实时展示系统状态。
每个环节核心是功能包创建、代码编写、CMake/package 配置、构建与运行,多实践即可熟悉 ROS2 开发流程