【多喝热水系列】从零开始的ROS2之旅——Day12 话题通信之系统实况信息小项目:C++

Hello 大家好!欢迎来到「多喝热水系列」ROS2学习之旅的第12天。 经过前几天的铺垫,我们已经掌握了ROS2话题通信的基本原理,今天就来动手实战一个实用小项目——实现系统实况信息的发布、订阅与QT可视化,把理论知识落地到实际应用中,同时掌握自定义通信接口、QT显示、系统信息获取这三个核心技能。

一、核心目标
今天我们的核心任务是掌握:

  • 自定义通信接口
  • QT显示
  • 系统信息的获取与发布

二、自定义通信接口
ROS2自带的消息接口无法满足我们传输“系统实况信息”的需求(需要包含时间戳、主机名、CPU使用率等自定义字段),因此第一步必须自定义一个msg消息接口,用于存储和传输系统状态数据。

首先创建一个名为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                #网路接收数据总量

#消息接口定义文件

CMAKELISTS

cmake_minimum_required(VERSION 3.8)
project(status_interfaces)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
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
)



if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

运行结果如下图所示:
请添加图片描述

三、QT显示

3.1 简单QT显示

#include <QApplication>
#include <QLabel>
#include <QString>

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; 
}

运行结果如下图所示:
请添加图片描述

四、 系统信息的订阅与发布

这一部分是今天的核心内容,我们将分为两个模块:用Python编写系统信息发布者(采集CPU、内存、网络等信息),用C++结合QT编写订阅者(订阅信息并在QT界面显示),实现完整的话题通信闭环。

4.1 系统信息的发布

import rclpy
from rclpy.node import Node
from status_interfaces.msg import SystemStatus          #导入自定义消息类型SystemStatus
import psutil                                           #导入psutil库以获取系统状态信息
import platform                                         #导入platform库以获取系统平台信息

class SystemStatusPub(Node):
    def __init__(self,node_name):
        super().__init__(node_name)
        self.status_publisher_=self.create_publisher(SystemStatus,'system_status',10)   #创建发布者,发布SystemStatus消息到'system_status'主题

        self.timer_=self.create_timer(1,self.timer_callback)                            #创建定时器,每秒调用一次timer_callback函数
    
    def timer_callback(self):

        cpu_percent=psutil.cpu_percent()                        #获取CPU使用率
        memory_info=psutil.virtual_memory()                     #获取内存使用情况
        net_io_counters=psutil.net_io_counters()                #获取网络I/O统计信息  

        msg=SystemStatus()                                      #创建SystemStatus消息实例
        msg.stamp=self.get_clock().now().to_msg()               #设置消息的时间戳为当前时间      
        msg.host_name=platform.node()                           #获取主机名
        msg.cpu_percent=cpu_percent                             #CPU使用率
        msg.memory_percent=memory_info.percent                  #内存使用率
        msg.memory_total=memory_info.total/1024/1024            #内存总量
        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=SystemStatusPub('system_status_publisher')         #创建SystemStatusPub节点实例
    rclpy.spin(node)                                        #进入节点的循环
    rclpy.shutdown()                                        #关闭rclpy


运行结果如下图所示:
请添加图片描述

4.2系统信息的订阅
订阅者我们用C++编写,结合QT实现可视化显示——订阅“system_status”话题的消息,将消息内容转换为QT字符串,在QLabel控件中显示,实现系统信息的实时可视化。

#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")
        {
            subscription_=this->create_subscription<SystemStatus>(
                "system_status",
                10,
                [&](const SystemStatus::SharedPtr msg)->void
                {
                    label_->setText(get_qstr_from_msg(msg));
                }
            );
//创建订阅者,订阅system_status话题,消息类型为SystemStatus,队列长度为10,回调函数为lambda表达式,get_qstr_from_status的作用是将msg转换为QString类型


            label_= new QLabel(get_qstr_from_msg(std::make_shared<SystemStatus>()));

            label_->show();
        /*创建一个QLabel对象,并将其显示出来,get_qstr_from_msg(std::make_shared<SystemStatus>())的作用是将msg转换为QString类型,
        std::make_shared<SystemStatus>()创建的shared_ptr智能指针,核心用途是自动管理SystemStatus对象内存、安全向函数传递对象,
        避免内存泄漏和野指针问题。       */        

        }
        

    QString get_qstr_from_msg(const SystemStatus::SharedPtr msg)
    {
    std::stringstream show_str;//创建字符串流对象
    show_str
    << "==============系统状态信息:==============\n"    
    << "Timestamp: " << msg->stamp.sec << " s " << msg->stamp.nanosec << " ns\n"
    << "Host Name: " << msg->host_name << "\n"
    << "CPU Percent: " << msg->cpu_percent << " %\n"
    << "Memory Percent: " << msg->memory_percent << " %\n" 
    << "Memory Total: " << msg->memory_total << " MB\n"
    << "Memory Available: " << msg->memory_available << " MB\n"
    << "Network Sent: " << msg->net_sent << " MB\n"
    << "Network Received: " << msg->net_recv << " MB\n";
    return QString::fromStdString(show_str.str());
    } //将SystemStatus消息转换为QString类型的函数

    private:
        rclcpp::Subscription<SystemStatus>::SharedPtr subscription_;//订阅者对象
        QLabel* label_;//标签对象


};









int main(int argc, char **argv)
{   
    rclcpp::init(argc, argv);
    QApplication app(argc, argv);

    auto node = std::make_shared<SysStatusDisplay>();

    std::thread spin_thread([&]() { rclcpp::spin(node); });
    spin_thread.detach();

    app.exec();
    rclcpp::shutdown();
    return 0;

}

运行结果如下图所示:
请添加图片描述

五、今日总结
今天我们完成了一个完整的ROS2话题通信小项目,从自定义消息接口,到系统信息的发布、订阅,再到QT可视化显示,每一步都贴合实际应用,既巩固了之前学的话题通信知识,又掌握了新的技能。
注意:
在运行时出现问题,可以试先开服务端再开订阅端。

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐