0. 前言

1. 项目代码的使用

  • 上一篇博客中已经介绍了创建工作空间、功能包等内容,这里不再赘述。
  • 克隆项目代码之后,执行:
      source install/setup.sh
      ros2 run ros2_qt_rviz2 ros2_qt_rviz2
    
  • 效果如下(这里订阅的是 Livox 雷达的点云话题,关于 Livox 雷达的使用再上一篇博客也介绍过了):

2. 代码知识点解析

  • 借助 QT 的多线程显示这个 rviz 界面,因为最终软件的界面是很复杂的,使用多线程可以避免不必要的阻塞,所以 QThread 基本的使用你需要了解,否则代码看上去一头雾水。LidarView 类继承自 QThread,主要流程如下图所示:


  • app_->processEvents();rviz_common::RenderPanel 变量初始化之前和之后是必不可少的,它可替换为 QApplication::processEvents(); ,这样可以省去从一层层传入 main.cpp 中的 QApplication 类型的变量。
  • 为了确保 rviz_common::ros_integration::RosNodeAbstractionIface::WeakPtr _rvizRosNode; 该变量不被销毁,必须让一个 std::shared_ptr<rviz_common::ros_integration::RosNodeAbstraction> 类型的变量指向它,后者在,前者就在,后者无,前者也就没了,所以保证后者的生命周期更长,在代码中这样写(详细知识点可以去了解一下 C++ 的智能指针 std::weak_ptr):
      std::shared_ptr<rviz_common::ros_integration::RosNodeAbstraction> _rvizRosNodeTmp;
      rviz_common::ros_integration::RosNodeAbstractionIface::WeakPtr _rvizRosNode;
      _rvizRosNodeTmp = std::make_shared<rviz_common::ros_integration::RosNodeAbstraction>("rviz_render_node");
      _rvizRosNode = _rvizRosNodeTmp;
    
  • Qt 的事件循环与 ROS2 循环存在区别:
    • 在Qt中,你需要让Qt的事件循环运行,以便你的应用程序能够响应用户输入和其他事件。如果你只运行rclcpp::spin_some()并睡眠,那么Qt的事件就可能无法得到处理,导致你的应用程序无法响应用户的操作。
    • 另外,rclcpp::spin_some()是ROS2中的一个函数,它运行ROS的事件循环。它处理ROS相关的事情,比如处理传入的消息和调用服务。但是它并不处理Qt的事件。
    • 所以,在Qt应用中使用ROS2时,你通常需要同时运行Qt的事件循环和ROS2的事件循环。你需要调用QApplication::processEvents()来处理Qt的事件,并且调用rclcpp::spin_some()来处理ROS2的事件。
      rclcpp::WallRate loop_rate(10); // 10HZ
      while (rclcpp::ok())
      {
        QApplication::processEvents();
        rclcpp::spin_some(_rvizRosNode.lock()->get_raw_node());
        loop_rate.sleep();
      }
      rclcpp::shutdown();
      
  • rviz_common::RenderPanel 的父类是 QWidget,所以我在 mainwindow.cpp 中添加 ui 时是像下面这样,线程类 lidarView 的成员变量 _render_panel 是个智能指针,通过 get() 函数可以获取 rviz_common::RenderPanel 类型的变量,绕了一圈最终绕回到 QWidget 了,这部分理解了,才能在官方的 Demo 上进行改动:

      MainWindow::MainWindow(QApplication * app, QWidget *parent)
      : QMainWindow(parent)
      , ui(new Ui::MainWindow)
      , _app(app)
      {
          ui->setupUi(this);
    
          lidarView = new LidarView();
          ui->scrollArea->setWidget(lidarView->_render_panel.get());
      }