【ROS2学习笔记】话题通信篇:话题通信项目实践——系统状态监测与可视化工具

前言

本系列博文是本人的学习笔记,自用为主,不是教程,学习请移步其他大佬的相关教程。主要学习途径为@鱼香ROS大佬的教程,欢迎各位大佬交流学习,若有错误,轻喷。

一、项目背景与准备

需制作系统状态监测与可视化工具 ,结合 ROS2 话题通信、Python(获取并发布系统信息)、C++/Qt(订阅并界面显示)实现。首先创建工作空间:在 chapt3 目录下执行:

bash 复制代码
mkdir -p chapt3/topic_practice_ws/src
cd chapt3/topic_practice_ws
colcon build  # 初始化工作空间

二、步骤 1:自定义通信接口(消息类型)

ROS2 内置接口无法满足 "系统状态(CPU、内存、网络等)传输" 需求,需自定义消息接口。

1. 创建接口功能包

topic_practice_ws/src 目录下执行:

bash 复制代码
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,内容如下:

bash 复制代码
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 还支持 boolbytecharint8/uint8 等 9 种基础类型。

3. 注册消息文件(修改 CMakeLists.txt

status_interfaces/CMakeLists.txt 中添加:

bash 复制代码
# 查找依赖
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 中添加:

bash 复制代码
<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 目录下执行:

bash 复制代码
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,代码如下:

python 复制代码
import rclpy
from rclpy.node import Node
from status_interfaces.msg import SystemStatus  # 导入自定义消息
import psutil  # 系统信息获取库
import platform

class SysStatusPub(Node):
    def __init__(self, node_name):
        super().__init__(node_name)
        # 创建发布者:话题名 sys_status,队列大小 10
        self.status_publisher_ = self.create_publisher(SystemStatus, 'sys_status', 10)
        # 创建定时器:1 秒调用一次 timer_callback
        self.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_percent
        msg.memory_percent = memory_info.percent
        msg.memory_total = memory_info.total / 1024 / 1024  # 字节转 MB
        msg.memory_available = memory_info.available / 1024 / 1024
        msg.net_sent = net_io_counters.bytes_sent / 1024 / 1024
        msg.net_recv = net_io_counters.bytes_recv / 1024 / 1024

        self.get_logger().info(f'发布:{str(msg)}')  # 日志输出
        self.status_publisher_.publish(msg)  # 发布消息

def main():
    rclpy.init()  # 初始化 rclpy
    node = SysStatusPub('sys_status_pub')
    rclpy.spin(node)  # 循环运行节点(等待回调)
    rclpy.shutdown()  # 关闭 rclpy

if __name__ == '__main__':
    main()

在 ROS2 的 ament_python 类型包 中,setup.pyentry_points 是核心配置:它负责告诉 ROS2:

  1. 节点命令名 (比如 sys_status_pub,用于 ros2 run 调用);
  2. 对应的 Python 文件 (比如 status_publisher/sys_status_pub.py);
  3. 文件中的入口函数 (比如 main 函数)。

默认用 ros2 pkg create 生成的 setup.py 中,entry_points 是空的(如下),必须手动补充:

python 复制代码
# 默认生成的空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 目录下执行:

python 复制代码
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,代码如下:

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.txthello_qt

status_display/CMakeLists.txt 中添加:

bash 复制代码
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_qt
  DESTINATION 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)类定义与消息转换
cpp 复制代码
#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 事件循环)
cpp 复制代码
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();  // 关闭 ROS2
    return result;
}

6. 配置 CMakeLists.txtsys_status_display

status_display/CMakeLists.txt 中添加:

bash 复制代码
# 生成 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_qt
  sys_status_display
  DESTINATION lib/${PROJECT_NAME})

7. 运行显示节点

  • 构建:colcon build --packages-select status_display
  • 生效环境:source install/setup.bash
  • 启动流程:
    1. 先启动发布节点ros2 run status_publisher sys_status_pub
    2. 再启动显示节点ros2 run status_display sys_status_display,界面会实时显示系统状态数据。

五、整体流程回顾

  1. 自定义接口 :创建 status_interfaces 包,定义 SystemStatus 消息,生成跨语言接口代码。
  2. 发布系统信息 :创建 status_publisher 包(Python),用 psutil 采集信息,通过 /sys_status 话题发布。
  3. 订阅并显示 :创建 status_display 包(C++/Qt),订阅 /sys_status 话题,用 Qt 界面实时展示系统状态。

每个环节核心是功能包创建、代码编写、CMake/package 配置、构建与运行,多实践即可熟悉 ROS2 开发流程

相关推荐
hssfscv2 小时前
JAVA学习笔记——9道综合练习习题+二维数组
java·笔记·学习
charlie1145141916 小时前
精读 C++20 设计模式:行为型设计模式 — 访问者模式
c++·学习·设计模式·访问者模式·c++20
长路归期无望6 小时前
C语言小白实现多功能计算器的艰难历程
c语言·开发语言·数据结构·笔记·学习·算法
知识分享小能手6 小时前
微信小程序入门学习教程,从入门到精通,微信小程序常用API(上)——知识点详解 + 案例实战(4)
前端·javascript·学习·微信小程序·小程序·html5·微信开放平台
yuxb738 小时前
Ceph 分布式存储学习笔记(二):池管理、认证和授权管理与集群配置(下)
笔记·ceph·学习
悠哉悠哉愿意8 小时前
【ROS2学习笔记】话题通信篇:python话题订阅与发布
笔记·学习·ros2
bruk_spp9 小时前
从pty驱动学习tty设备驱动加载
linux·学习
东方芷兰9 小时前
JavaWeb 课堂笔记 —— 20 SpringBootWeb案例 配置文件
java·开发语言·笔记·算法·log4j·intellij-idea·lua
楼田莉子9 小时前
vscode搭建C/C++配置开发环境
c语言·开发语言·c++·vscode·学习·编辑器