3.4.1 工程架构设计
需求背景:
第一,通过这个小工具可以看到系统的实时状态信息包括记录信息的时间、主机名称、CPU使用率、内存使用率、内存总大小、剩余内存、网络接收数据量和网络发送数据量;
第二,要有一个简单的界面,可以将系统信息显示出来;
第三,要能在局域网内其他主机上查看数据
设计:
第一,要能获取系统状态信息;Python库psutils
第二,要有一个展示界面:Qt
第三,要能共享数据。ROS2话题
3.4.2 自定义通信接口
在topic_practice_ws/src下面创建一个新的功能包
ros2 pkg create status_interfaces --dependencies builtin_interfaces rosidl_default_generators --license Apache-2.0
其中2个依赖:
builtin_interfaces 可以使用时间接口Time
rosidl_default_generators 是用来把自定义消息转换为C++、python的源码。
在功能包下新建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.txt,对接口文件进行注册,声明为接口文件,并增加builtin_interfaces 依赖。
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(builtin_interfaces REQUIRED)
find_package(rosidl_default_generators REQUIRED)
#cmake 函数,来自依赖rosidl_default_generators,用于讲msg消息接口定义文件转换成库或者头文件
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 filesset(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 filesset(ament_cmake_cpplint_FOUND TRUE)ament_lint_auto_find_test_dependencies()
endif()ament_package()
修改packages.xml中,增加声明
<member_of_group>rosidl_interface_packages</member_of_group>
接下来构建,并查看
bohu@bohu-TM1701:~/3/topic_practice_ws$ colcon build
Starting >>> status_interfaces
Finished <<< status_interfaces [6.51s] Summary: 1 package finished [6.88s]
bohu@bohu-TM1701:~/3/topic_practice_ws$ source install/setup.bash
bohu@bohu-TM1701:~/3/topic_practice_ws$ ros2 interface show status_interfaces/msg/SystemStatus
builtin_interfaces/Time stamp # 记录时间戳int32 secuint32 nanosec
string host_name # 系统名称
float32 cpu_percent # CPU使用率
float32 memory_percent # 内存使用率
float32 memory_total # 内存总量
float32 memory_available # 剩余有效内存
float64 net_sent # 网络发送数据总量
float64 net_recv # 网络接收数据总量
还可以去install/status_interfaces/include下面,是否生成了C++文件及python库。
现在已经完成了自定义消息接口。
3.4.3 系统信息获取与发布
创建功能包status_publisher。
ros2 pkg create status_publisher --build-type ament_python --dependencies rclpy status_interfaces --license Apache-2.0
在功能包下创建文件sys_status_pub.py.代码如下:
import rclpy
from status_interfaces.msg import SystemStatus
from rclpy.node import Node
import psutil
import platformclass SysStatusPub(Node):def __init__(self, node_name):super().__init__(node_name)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_percentmsg.memory_percent = memory_info.percentmsg.memory_total = memory_info.total/1024/1024msg.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()node = SysStatusPub('sys_status_pub')rclpy.spin(node)rclpy.shutdown
定义了SysStatusPub类,继承了Node.初始化创建发布者status_publisher_跟定时器timer_。定时器每秒调用一次回调timer_callback来发布数据,timer_callback内部使用了psutil获取系统信息。还有单位转换为MB.
接下来去setup.py中去注册节点sys_status_pub。编译运行。
ros2 run status_publisher sys_status_pub
bohu@bohu-TM1701:~/3/topic_practice_ws$ ros2 run status_publisher sys_status_pub
[INFO] [1736234385.803543118] [sys_status_pub]: 发布:status_interfaces.msg.SystemStatus(stamp=builtin_interfaces.msg.Time(sec=1736234385, nanosec=761086615), host_name='bohu-TM1701', cpu_percent=7.1, memory_percent=77.4, memory_total=7824.6953125, memory_available=1771.0078125, net_sent=484.4188470840454, net_recv=5299.850911140442)
[INFO] [1736234386.762844262] [sys_status_pub]: 发布:status_interfaces.msg.SystemStatus(stamp=builtin_interfaces.msg.Time(sec=1736234386, nanosec=760874264), host_name='bohu-TM1701', cpu_percent=10.2, memory_percent=77.3, memory_total=7824.6953125, memory_available=1774.328125, net_sent=484.4210395812988, net_recv=5299.852033615112)
[INFO] [1736234387.763212697] [sys_status_pub]: 发布:status_interfaces.msg.SystemStatus(stamp=builtin_interfaces.msg.Time(sec=1736234387, nanosec=760907123), host_name='bohu-TM1701', cpu_percent=9.0, memory_percent=77.2, memory_total=7824.6953125, memory_available=1781.57421875, net_sent=484.4244565963745, net_recv=5299.8582944869995)
可以看到能够获取系统状态并发布了。
3.4.4 在功能包中使用QT
这个就是为了熟悉下QT的使用。
先在 src下创建功能包status_display
ros2 pkg create status_display --build-type ament_cmake --dependencies rclcpp status_interfaces --license Apache-2.0
测试类hello_qt.cpp,效果如下
消除引用警告,修改c_cpp_properties.json
{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/opt/ros/humble/include/**","/usr/include/x86_64-linux-gnu/qt5/**"],"defines": [],"compilerPath": "/usr/bin/gcc","cStandard": "c17","cppStandard": "gnu++17","intelliSenseMode": "linux-gcc-x64"}],"version": 4
}
3.4.5 订阅数据并用QT显示
在src/status_display/src下新建sys_status_display.cpp.代码如下
#include <QApplication>
#include <QLabel>
#include <QString>
#include <rclcpp/rclcpp.hpp>
#include <status_interfaces/msg/system_status.hpp>using SystemStatus = status_interfaces::msg::SystemStatus;
using namespace std;class SysStatusDisplay :public rclcpp::Node
{
private:/* data */rclcpp::Subscription<SystemStatus>::SharedPtr subscriber_;QLabel* label_;
public:SysStatusDisplay(/* args */):Node("sys_status_display"){label_ = new QLabel();subscriber_ = this->create_subscription<SystemStatus>("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 = std::make_shared<SysStatusDisplay>();std::thread spin_thread([&]()->void{rclcpp::spin(node);});spin_thread.detach();app.exec();//执行,阻塞代码rclcpp::shutdown();return 0;
}
SysStatusDisplay 类继承来Node节点,定义了两个属性:label_ 用于显示,subscriber_ 订阅话题。
接着对它们进行初始化,subscriber_ 使用了lambda表达式作为回调函数。处理显示字符串放在方法get_qstr_from_msg()。
主函数设计比较巧妙,因为之前默认的rclcpp::spin(node);会阻塞执行。app.exec();也会阻塞程序运行。无论调整顺序不满足需求导致无法正常显示,所以小鱼老师讲spin单独放到一个线程中进行处理。界面退出后app.exec()退出,接着调用rclcpp::shutdown();程序正常退出。
修改CMakeLists.txt,添加节点及依赖。
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(hello_qt src/hello_qt.cpp)
target_link_libraries(hello_qt Qt5::Widgets)
add_executable(sys_status_display src/sys_status_display.cpp)
target_link_libraries(sys_status_display Qt5::Widgets)# 对于非ROS功能包使用Cmake原生指令进行链接库
ament_target_dependencies(sys_status_display rclcpp status_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 filesset(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 filesset(ament_cmake_cpplint_FOUND TRUE)ament_lint_auto_find_test_dependencies()
endif()
install(TARGETS hello_qt sys_status_display
DESTINATION lib/${PROJECT_NAME})ament_package()
构建并运行运行,依赖上一个sys_status_pub节点发布话题,
ros2 run status_publisher sys_status_pub
ros2 run status_display sys_status_display
效果如下:
本节一下子接触的知识点还是挺多的。