【多喝热水系列】从零开始的ROS2之旅——Day12 话题通信之系统实况信息小项目:C++
【多喝热水系列】从零开始的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可视化显示,每一步都贴合实际应用,既巩固了之前学的话题通信知识,又掌握了新的技能。
注意:
在运行时出现问题,可以试先开服务端再开订阅端。
更多推荐



所有评论(0)