ROS2 Foxy + Qt5 on Linux Platform
简介
本文章简单介绍了如何在Ubuntu 20.04上编译、运行ROS2 Foxy + Qt5。
可以将Qt5 cmake工程放到ROS2 workspace里,用colcon编译ROS2包 + Qt5 cmake工程;也可以从QtCreator打开ROS2包,用QtCreator编译ROS2包 + Qt5 cmake工程。
环境
平台:Ubuntu 20.04
ROS2版本:Foxy
Qt5版本:5.12.8
cmake版本:3.16.3
C编译器:GNU 9.3.0
CXX编译器:GNU 9.3.0
ament_cmake版本:0.9.8
colcon编译ROS2包 + Qt5 cmake工程
- 在ROS2 workspace里新建一个包,例如qt_example。
- 打开qtcreator,创建Qt5 cmake工程,工程保存在上一个步骤创建的qt_example里。
- 修改Qt5 cmake工程的目录结构,以适应qt_example包的目录结构,如下图所示:
- 修改CMakeLists.txt和package.xml。CMakeLists.txt融合了qt_example包、Qt5的cmake要素(在本例中,qt_example包引用了我自己写的ROS2 algo包);package.xml声明了colcon编译工具需要知道的qt_example包的依赖。CMakeLists.txt和package.xml详见附录。
- 用"colcon build"命令编译ROS2 workspace。
- 新开一个终端,输入"source /path_to_your_ROS2_ws/install/local_setup.bash",再输入"ros2 run qt_example qt_example_node",即可运行Qt GUI。
附录
- CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(qt_example)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
# Note: Compilation flag -fPIC is necessary. Otherwise, you'll receive error "You must build your code with position independent code if Qt was built with -reduce-relocations."
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic -fPIC)
endif()
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(algo REQUIRED)
find_package(Qt5 COMPONENTS Widgets REQUIRED)
# Note that ament_target_dependencies() in algo cannot export Boost::thread to downstream. As a result, qt_example_node, as a downstream of libalgo.so, needs to explicitly find_package() for Boost again so that qt_example_node is able to link to Boost::thread.
find_package(Boost 1.71.0 REQUIRED COMPONENTS thread)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
include_directories(
include
${Qt5Widgets_INCLUDE_DIRS}
)
# Note: moc is not processing mainwindow.h (included by main.cpp and mainwindow.cpp unless it is in the same folder as the source files which included it). As a result, we need to explicitly call qt_wrap_cpp() on mainwindow.h (not on mainwindow.cpp) and then include the resulting file in the call to add_executable(). See link: https://stackoverflow.com/questions/19761767/qt-5-cmake-fails-with-undefined-reference-to-vtable-on-hello-world-with-inc-sr
qt5_wrap_cpp(header_SRC include/${PROJECT_NAME}/mainwindow.h)
add_executable(${PROJECT_NAME}_node
src/main.cpp
src/mainwindow.cpp
src/mainwindow.ui
${header_SRC}
)
# Note that ament_target_dependencies() in algo cannot export Boost::thread to downstream. As a result, qt_example_node, as a downstream of libalgo.so, needs to explicitly ament_target_dependencies() for Boost again so that qt_example_node is able to link to Boost::thread.
ament_target_dependencies(${PROJECT_NAME}_node rclcpp algo Boost)
target_link_libraries(${PROJECT_NAME}_node
Qt5::Widgets
)
install(TARGETS ${PROJECT_NAME}_node
DESTINATION lib/${PROJECT_NAME}
)
ament_package()
- package.xml
<?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>qt_example</name>
<version>0.0.0</version>
<description>This is a ROS2 demo package which demostrates how to build and run Qt5 package within ROS2 workspace. Qt5 project has been adjusted in compliance with ROS2 package layout. Run "colcon build" to build this package.</description>
<maintainer email="maintainer@mailbox.com">feng_wang</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<build_depend>algo</build_depend>
<build_depend>Qt5</build_depend>
<exec_depend>Qt5</exec_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
- mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "algo/rc_interface/rc_interface.hpp"
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_jog_start_clicked<