0. 前言

  • 网上对于 QT5 开发 ROS2 界面的资料几乎没有,我从 Github 上的一个仓库 Fork 了一个,进行了修改,形成一个模板,可以在此模板的基础上进行修改开发自己的 ROS2 GUI 。

  • Github 链接ros2_qt_template

1. QT5 和 ROS 插件安装

  1. 下载 .run 文件,推荐使用清华源在线安装包,速度很快: 下载链接
  2. 执行安装:
     chmod a+x qt-unified-linux-x64-online.run
     ./qt-unified-linux-x64-online.run --mirror https://mirrors.tuna.tsinghua.edu.cn/qt
    
  3. Github 下载对应版本并安装 ros_qtc_plugin ,注意需要以 root 权限执行可以将插件安装到系统文件夹中,否则只能安装到用户文件夹:

     sudo /opt/Qt/Tools/QtCreator/bin/qtcreator
    

2. 创建 ROS2 QT 项目

注意:似乎只支持 QT5,对于 QT6,结合 ROS2 无法编译通过。

  1. 新建文件夹 ws_qt
  2. 创建工作空间:

  3. 修改文件夹筛选器,显示出 src 文件夹,右键 src 文件夹,新建 ROS 功能包:

  4. 进入 src 文件夹下:

     git clone https://github.com/SEUZTh/ros2_qt_template.git
    
  5. 在 QT 中编译项目;

  6. 运行节点:

     source install/setup.sh
     ros2 run ros2_qt_demo ros2_qt_demo
    

3. 模板代码解析

3.1 CMakeLists.txt

CMakeLists.txt 解析(这里没有列出全部代码,只是代码片段):

# 可以修改为自己想要的功能包名称,同时在 package.xml 中也需要修改名称
project(ros2_qt_template)

# 添加需要用到的 ROS2 包
find_package(rclcpp REQUIRED)
# find_package(sensor_msgs REQUIRED)
# find_package(cv_bridge REQUIRED)

# 添加依赖的 ROS2 库,上面添加的包在这里也需要添加才能使用
ament_target_dependencies(${PROJECT_NAME} rclcpp  Boost)

3.2 rclcomm.cpp

对应的头文件就不说了,直接来看 rclcomm.cpp 中的函数定义:

#include "rclcomm.h"

// 这里需要对发布者和订阅者进行初始化
// 如果一个可执行文件中定义多个节点,ROS2 的初始化不能放在这里,因为同一个可执行文件中不能初始化两次
rclcomm::rclcomm()  {
  node=rclcpp::Node::make_shared("ros2_qt_demo");
  _publisher =
      node->create_publisher<std_msgs::msg::Int32>("ros2_qt_demo_publish", 10);
  _subscription = node->create_subscription<std_msgs::msg::Int32>("ros2_qt_demo_publish", 10,std::bind(&rclcomm::recv_callback,this,std::placeholders::_1));
  this->start();
}

// run()函数是QThread类中的虚函数,它在新的线程中被调用,不需要你手动调用
void rclcomm::run(){
    std_msgs::msg::Int32 pub_msg;
    pub_msg.data=0;
    rclcpp::WallRate loop_rate(10); // 10HZ
    while (rclcpp::ok())
    {
        pub_msg.data++;
        _publisher->publish(pub_msg);
        rclcpp::spin_some(node);
        loop_rate.sleep();
    }
    rclcpp::shutdown();
}

// 回调函数,其中实现你想实现的功能即可,这里实现的功能是触发 QT 信号
void rclcomm::recv_callback(const std_msgs::msg::Int32::SharedPtr msg){
    emitTopicData("I head from ros2_qt_demo_publish:"+QString::fromStdString(std::to_string(msg->data)));
}

3.3 mainwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    QImage img;
    img.load(":/icon/images/foxy.jpg");
    img.scaled(ui->label->width(),ui->label->height());
    ui->label->setPixmap(QPixmap::fromImage(img).scaled(ui->label->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));

    // 初始化 ROS2
    int argc=0;
    char **argv=NULL;
    rclcpp::init(argc,argv);

    // 实例化节点(可以在这里实例化多个节点,用于不同的功能)
    commNode = new rclcomm();
    connect(commNode, SIGNAL(emitTopicData(QString)), this, SLOT(updateTopicInfo(QString)));
}
void MainWindow::updateTopicInfo(QString data){
    ui->label_4->clear();
    ui->label_4->setText(data);
}
MainWindow::~MainWindow()
{
    delete ui;
}