话题通信实践

使用话题通信实现一个系统信息监视工具

消息接口是定义通信数据结构的规则,消息类型是根据规则生成的具体代码实现,供节点使用,这里由于没有对应的消息类型所以我们需要先自定义一个消息接口。

需要两个基础包

builtin_interfaces:ROS 2 自带的一个接口包,里面定义了一些基础消息类型。

rosidl_default_generators:一个构建工具包,提供生成消息/服务/动作接口代码的 CMake 宏和逻辑。

自定义的消息接口如下

builtin_interfaces/Time stamp # 记录时间戳
string host_name # 主机名字
float32 cpu_percent # CPu使用率
float32 memory_percent # 内存使用率
float32 memory_total # 内存总大小
float32 memory_available # 内存总大小
float64 net_sent # 网络发送数据总量imp8Mb
float64 net_recv # 网络数据接受总量M

Cmake函数,将消息接口转换为库或头文件,这样编译时生成对应的 C++/Python 类型

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/SystemStatus.msg"
  DEPENDENCIES builtin_interfaces
)

<member_of_group>rosidl_interface_packages</member_of_group> :它声明当前包是一个 ROS 2 接口包,用于提供消息、服务或动作定义。

image

以下是项目将自定义的消息接口生成对应的头文件

image

image

ros2 pkg create status_publisher --build-type ament_python --dependencies rclpy status_interfaces --license Apache-2.0创建发布者功能包,上面创建的是消息接口的功能包

每次打开新的终端都要刷新环境变量

import rclpy
import psutil
import platform
from status_interfaces.msg import SystemStatus
from rclpy.node import Node

class StatusPublisher(Node):
    def __init__(self,node_name):
        super().__init__(node_name)
        # 话题名称为sys_status,发布者为节点内部成员
        self.status_publisher = self.create_publisher(
            SystemStatus,'sys_status',10
        )
        self.timer_ = self.create_timer(1.0,self.timer_callback)
    def timer_callback(self):
        # 获取信息
        cpu_percent = psutil.cpu_percent()
        memory_info = psutil.virtual_memory()
        net_io_counters = psutil.net_io_counters()

        # 输入消息
        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
        # 将B转换为MB
        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'publish {str(msg)}')
        self.status_publisher.publish(msg)
def main():
    rclpy.init()
    #节点名称为statusPub
    node = StatusPublisher('statusPub') 
    rclpy.spin(node)
    rclpy.shutdown()

image

使用QT创建可视化窗口,将订阅者获取得到的消息进行可视化展示

cmake_minimum_required(VERSION 3.8)
project(status_display)

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(rclcpp REQUIRED)
find_package(status_interfaces REQUIRED)
find_package(Qt5 REQUIRED COMPONENTS Widgets)

add_executable(statusDisplay src/status_display.cpp)

ament_target_dependencies(statusDisplay rclcpp status_interfaces)

target_link_libraries(statusDisplay Qt5::Widgets)

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()

install(TARGETS statusDisplay
  DESTINATION lib/${PROJECT_NAME}
)

ament_package()
#include<QApplication>
#include <QLabel>
#include <QString>
#include <rclcpp/rclcpp.hpp>
#include <status_interfaces/msg/system_status.hpp>
using namespace std;
using namespace rclcpp;
using SystemStatus = status_interfaces::msg::SystemStatus;

class StatusDisplay: public Node{
private:
    Subscription<SystemStatus>::SharedPtr subscriber;
    QLabel *label;
public:
    StatusDisplay():Node("statusDisplay"){
        label =new QLabel();
        subscriber = this->create_subscription<SystemStatus>(
            // lambada表达式创建匿名函数,每获取到一条消息就转换为对应字符串打印到界面上
            "sys_status",10,[&](const SystemStatus::SharedPtr msg) -> void{
                label->setText(get_qstr_from_msg(msg));
            } 
        );
        label->setText(get_qstr_from_msg(std::make_shared<SystemStatus>()));
        label->show();
    };
    // 将获取到的消息转为我们要打印的字符串
    QString get_qstr_from_msg(const SystemStatus::SharedPtr msg){
        stringstream show_str;
        show_str
        << "===新提供状态可视化显示工具=====\n"
        << "数据 时间:\t" << msg->stamp.sec << "\ts\n"
        << "主机名字:\t" << msg->host_name << "\t\n"
        << "CPU使用率:\t" << msg->cpu_percent <<"\t%\n"
        << "内存使用率:\t" << msg->memory_percent << "\t%\n"
        << "内存总大小:\t" << msg->memory_total << "\tMB\n"
        << "剩余有效内存:\t" << msg->memory_available << "\tMB\n"
        << "网络发送量:\t" << msg->net_sent << "\tMB\n"
        << "网络接受量:\t" << msg->net_recv << "\tMB\n"
        << "===============================";
        return QString::fromStdString(show_str.str());
    };

};


int main(int argc,char *argv[]){
    rclcpp::init(argc,argv);
    QApplication app(argc,argv);
    auto node = make_shared<StatusDisplay>();
    // 由于执行ROS和Qt界面都会堵塞系统,需要再开一个线程进行异步执行
    thread spin_thread([&]()->void{
        rclcpp::spin(node);
        rclcpp::shutdown();
    });
    spin_thread.detach();
    app.exec();
    return 0;
}

image

posted @ 2025-09-24 13:50  突破铁皮  阅读(7)  评论(0)    收藏  举报