ROS2订阅和发布 自定义消息类型 打开终端在ros2_ws/src下输入命令
1 2 3 4 ros2 pkg create status_interfaces \ --build-type ament_cmake \ --dependencies rosidl_default_generators builtin_interfaces \ --license Apache-2.0
上面的指令用于创建一个名叫 status_interfaces 的功能包,并为其添加 builtin_interfaces 和 rosidl_default_generators 两个依赖。builtin_interfaces 是 ROS2 中已有的一个消息接口功能包,可以使用其时间接口Time,表示记录信息的时间。 rosidl_default_generators 用于将定义的消息文件转换为 C++、Python 源码的模块。
ROS2 中话题消息定义文件需要放置在功能包的 msg 目录下,文件名必须以大写字母开头,且只能是大写小写以及数字组成。接着在功能包下创建 msg 目录,并在该目录下新建文件 SystemStatus.msg 。
1 2 3 4 5 6 7 8 builtin_interfaces/Time stamp string host_name float32 cpu_percent float32 memory_percent float32 memory_total float32 memory_available float64 net_sent float64 net_recv
ros2 消息接口支持的数据类型共有9种:
bool
byte
char
string
float32,float64
int8,unint8
int16,unint16
int32,unint32
int64,unint64
定义好数据接口文件后,需要在 CMakeLists.txt 中对该文件进行注册,声明其为消息接口文件,并为其添加 builtin_interfaces 依赖,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 cmake_minimum_required (VERSION 3.8 )project (status_interface)if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang" ) add_compile_options (-Wall -Wextra -Wpedantic) endif ()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) set (ament_cmake_copyright_FOUND TRUE ) set (ament_cmake_cpplint_FOUND TRUE ) ament_lint_auto_find_test_dependencies() endif ()ament_package()
在功能包清单文件 package.xml 中添加申明。在package.xml 中添加 member_of_group 是为了声明该功能包是一个消息接口功能包,方便 ROS 2对其做额外处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?xml version="1.0" ?> <?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema" ?> <package format ="3" > <name > status_interface</name > <version > 0.0.0</version > <description > TODO: Package description</description > <maintainer email ="root@todo.todo" > root</maintainer > <license > Apache-2.0</license > <member_of_group > rosidl_interface_packages</member_of_group > <buildtool_depend > ament_cmake</buildtool_depend > <depend > rosidl_default_generators</depend > <depend > builtin_interfaces</depend > <test_depend > ament_lint_auto</test_depend > <test_depend > ament_lint_common</test_depend > <export > <build_type > ament_cmake</build_type > </export > </package >
接着来构建功能包,构建完成后,可以使用的命令来确定消息接口是否构建成功。
1 2 sourse install/setup.bash ros2 interface show status_interfaces/msg/SystemStatus
显示:
1 2 3 4 5 6 7 8 9 10 11 (ros2) root@GuFan:~/Desktop/ros2_ws# ros2 interface show status_interfaces/msg/SystemStatus builtin_interfaces/Time stamp int32 sec uint32 nanosec string host_name float32 cpu_percent float32 memory_percent float32 memory_total float32 memory_available float64 net_sent float64 net_recv
系统信息的获取与发布 在 src 目录下创建 status_publisher 功能包,并在功能包对应目录下创建 sys_status_pub.py 文件, 并添加消息接 口 status_interfaces 和客户端库 relpy 作为其依赖。
1 2 3 4 ros2 pkg create status_publisher \ --build-type ament_python \ --dependencies rclpу status_interfaces \ --license Apache-2.0
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import rclpyfrom rclpy.node import Nodefrom status_interface.msg import SystemStatusimport psutilimport 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 , 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 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() node = SysStatusPub("sys_status_pub" ) rclpy.spin(node) rclpy.shutdown()
在 setup.py 中对 sys_status_pub 节点进行注册。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from setuptools import find_packages, setuppackage_name = 'status_publisher' setup( name=package_name, version='0.0.0' , packages=find_packages(exclude=['test' ]), data_files=[ ('share/ament_index/resource_index/packages' , ['resource/' + package_name]), ('share/' + package_name, ['package.xml' ]), ], install_requires=['setuptools' ], zip_safe=True , maintainer='root' , maintainer_email='root@todo.todo' , description='TODO: Package description' , license='Apache-2.0' , tests_require=['pytest' ], entry_points={ 'console_scripts' : [ 'sys_status_pub = status_publisher.sys_status_pub:main' ], }, )
运行
1 ros2 run status_publisher sys_status_pub
打开新的终端,依次输入命令:
1 2 source install/setup.bashros2 topic echo /sys_status
到这里,便把系统状态获取以及发布部分完成了。
报错记录
ImportError: /root/miniconda3/envs/ros2/bin/../lib/libstdc++.so.6: version GLIBCXX_3.4.30’ not found`
意思:你的 Conda 环境里的 libstdc++.so.6 太旧 ,没有 GLIBCXX_3.4.30 这个符号(ROS 2 Jazzy 的 rclpy 模块需要)。
原因:ROS 2 Jazzy 本身是基于 系统的 GCC/libstdc++ 编译的。在 conda 环境里运行时优先加载了conda的libstdc++.so.6。结果就是版本对不上。
解决方法:强制使用系统的 libstdc++
1 export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libstdc++.so.6
订阅消息并通过QT显示 新建功能包
1 2 3 4 ros2 pkg create status_display \ --build-type ament_cmake \ --dependencies rclcpp status_interfaces \ --license Apache-2.0
在 src/status_display/src 下新建 sys_status_display.cpp ,编写代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #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>( "sys_status" , 10 , [&](const SystemStatus::SharedPtr msg) -> void { label_->setText (get_qstr_from_msg (msg)); }); label_ = new QLabel (get_qstr_from_msg (std::make_shared <SystemStatus>())); label_->show (); } QString get_qstr_from_msg (const SystemStatus::SharedPtr msg) { std::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 ()); } 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 ([&]() -> void { rclcpp::spin(node); }) ; spin_thread.detach (); app.exec (); rclcpp::shutdown (); return 0 ; }
ROS2 的 spin 会阻塞,所以单独开一个线程执行 rclcpp::spin(node)。进入 Qt 的事件循环,显示窗口。当窗口关闭时,停止 ROS2。