Qtcreator User-Interface with ROS
Qtcreator is a popular user interface integrated development environment (IDE) that can interface easily with ROS. It supports multiple platforms, such as x86 desktop, arm-based embedded devices, and mobile platforms such as Android and iOS. However, this tutorial will focus on using this tool to create a user interface with ROS integrated in it.
#### Why use Qtcreator and not ROS Qt (rqt)
rqt is great for ‘quick and dirty’ UI where-by you can fix together different plugins (or make your own plugin) in a dockable window. However, if you want to make highly customized UI with your own graphics asset, animation, and granular control, creating a Qt application is a better option.
Installation
Do not install the standard Qtcreator from their website, this version is meant for the more generic version of Qt for all application. We want instead to install the Qtcreator that has been tailored for ROS development.
Requirements
- Ubuntu 18.04 LTS (please check what is available in this website)
- ROS Melodic Desktop Full (if using Ubuntu 18.04 LTS)
Installation Steps
-
Download the online installer for Users (not Developers). This is the latest link for Ubuntu 18.04 but I suggest to Google
qtcreator ros install
yourself and download the latest stable release -
Run the installer through the command line terminal, e.g. if you download it to your ~/Downloads folder
$ chmod +x ~/Downloads/qtcreator-ros-bionic-latest-online-installer.run $ ~/./Downloads/qtcreator-ros-bionic-latest-online-installer.run
-
Select all the default configuration in the setting window. It should install Qtcreator at your home directory
-
Test your installation by launching the program
$ qtcreator-ros
A program window should open similar to this:
Installation Troubleshooting
-
Qt library files not found
if you see this error when launching
qtcreator-ros
:error while loading shared libraries: libQt5Core.so.5 cannot open object file: No such file or directory.
whereby a Qt .so file is not found, this means that either:
- You used the wrong installer file (the default qtcreator instead of qtcreator-ros for example, or the wrong Ubuntu version)
- You accidentally removed Qt library files. These should have been installed automatically when you installed ROS Melodic through the command
sudo apt install ros-melodic-desktop-full
. One possibility is that you used the Qt MaintenanceTool and selectedRemove all components
Make your own Hello World example
This section will guide you to create your first simple Hello-World Qt program
-
Run the
qtcreator-ros
application -
Create new project using
File -> New File or Project -> Application -> Qt Widgets Application
-
Name your project/application
-
If you installed correctly, the Kit will be detected automatically, if not this link might be useful
-
Name your main class. Although this is not the main function, it acts as the entry point of the application
-
Select finish
-
A project folder should be created, you can navigate with the menu on the left side.
-
Click the green arrow at the bottom left side or use
Ctrl + R
to build and run. You should see a blank application window like this one -
Close the program by clicking on the red cross on the
MainWindow
window. and navigate to themainwindow.ui
file underForms
on the left menu. It will automatically switch to theDesign
tab and open this UI editor tool -
The left menu is now switched to the basic UI widgets available. You can put them through drag and drop. Try to drag and drop these 4 types of widgets:
Buttons -> Push Button
Input Widgets -> Line Edit
Display Widgets -> Label
Display Widgets -> Text Browser
-
Some of the widgets have text that is displayed on them such as the
Label
andPush Button
, you can double-left-click on them and edit the display text -
On the right menu you should see a summary of all the objects and widgets that you have created. Here we can see the 4 widgets we added. An important distinction here is that the left column is the name of the object (unique entity) and the right column is the class name (not unique). Hence, you can have multiple labels, but these are based on
QLabel
class -
To drive the point home, try to rename the objects. It is advisable to give it meaningful name as you would name a variable. Also, remember to save often with
Ctrl + S
-
Run the program again
Ctrl + R
and you should see your widget. Try to type on the line edit widget
Qt communication framework
Similar to ROS’s communication framework, different Qt objects can communicate to each other through trigger events. However, instead of using Publisher/Subscriber system like in ROS, Qt uses signal/slot system. A signal is a message that is sent out (analogous to a publisher), and a slot receives a signal (analogous to a subscriber). Therefore, a slot function is similar to a callback function whereby you execute a piece of code whenever a new message is received.
There is no need to create a signal/slot object like in ROS, when you create any a class whose parent class is a QtObject, the inherited class will have the signal/slot capabilities. Signals and slots are represented as functions in a class. For example:
class AClass : public QObject {
Q_OBJECT
public:
AClass() {}
signals:
void mySignal1(int);
void mySignal2(float, int);
void mySignal3();
public slots:
void mySlot1(double);
void mySlot2();
private:
int memberVar;
};
Let’s take a more concrete example between two classes, one of them is the class above AClass
and another BClass
as defined below:
class BClass : public QObject {
Q_OBJECT
public:
BClass();
void run();
signals:
void mySignalFromB(double);
public slots:
private:
AClass* aClass;
};
void BClass::BClass() {
aClass = new AClass();
connect(this, SIGNAL(mySignalFromB(double)), aClass, SLOT(mySlot1(double)));
}
void BClass::run() {
std::cout << "B: sending signal from B" << std::endl;
double var = 1.0;
emit mySignalFromB(1.0);
}
void AClass::mySlot1(double msg) {
std::cout << "A: receiving signal from B to slot in A" << std::endl;
std::cout << "message value: " << msg << std::endl;
}
int main() {
BClass bClass;
bClass.run();
return 0;
}
The output of this program will be
B: sending signal from B
A: receiving signal from B to slot in A
message value: 1.0
Summary of important components in Qt communication
- signals are functions that send an argument that will be received by a slot
- signals do not perform any other function beside passing the arguments
- slot function is where you define events, akin to callbacks in ROS
- In the constructor, you need to setup which signals connect to which slot with the
connect
function by specifying the source object address, signal function, destination object address, and slot function - Use the
emit
command to send signals
You can read more about it here
How to integrate ROS in Qt application
Most GUIs have some sort of a while loop to continuously process the GUI and Qt works in a similar fashion. Therefore, you can’t put ros::spin()
inside the main function as you would normally do in a ROS node as the thread will be busy processing the GUI.
The approach we will take is to create a ROS node to handle the publishing and subscription, instantiate them in the mainwindow.cpp
class and send them to be processed concurrently on different threads.
Steps:
-
Add ROS include in the .pro file
The project’s .pro file is analogous to CMakeLists.txt where you specify dependencies. Here’s a sample of the libraries you need for ROS development
QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport INCLUDEPATH += /opt/ros/melodic/include LIBS += -L/opt/ros/melodic/lib -lroscpp -lroslib -lrosconsole -lroscpp_serialization -lrostime -lrviz
-
ros::NodeHandle
will be created as a member of themainwindow
class, do not create new one elsewhere -
Create a class for subscriber:
-
It will have a pointer to a nodehandle (
ros::NodeHandle
) as a member, and the address of the nodehandle object will be passed to this member variable in the constructor -
It will have a slot function that initializes the
ros::Subscriber
and a while loop that runsemit
signals andros::spinOnce()
// run ROS ros::Rate loop_rate(20); while (ros::ok()) { emit signal_data_callback(msg.data); ros::spinOnce(); loop_rate.sleep(); }
-
-
Create a class for publisher
- It will have a pointer to a nodehandle (
ros::NodeHandle
) as a member, and the address of the nodehandle object will be passed to this member variable in the constructor - It will have a slot function that runs the
publish()
function
- It will have a pointer to a nodehandle (
-
Edit the
MainWindow
class, this is where most of the action happens-
Create a
ros:NodeHandle
, a pointer that handles the subscriber/publisher class that you made, andQThread
objects for each of your subscriber/publisher classprivate: Ui::MainWindow *ui; ros::NodeHandle nh_; SubscriberNode *p_subscriber_node_; PublisherNode *p_publisher_node_; QThread p_subcriber_node_thread_; QThread p_publisher_node_thread_;
-
Create slots for your GUI elements by right clicking on them in the edit window –>
Go to slot...
, this will create a member functionprivate slots: void on_Button_clicked();
-
Create slots to receive messages from your subscriber class as a member function, add the data type as the function parameter (e.g. float, int, bool)
private slots: void slot_msgFb(float);
-
Create signals to send messages to your publisher class as a member function, add the data type as the function parameter (e.g. float, int, bool)
signals: void sigSignal(float);
-
Use the
connect
function to connect slots and signals together in the class constructorconnect(this, SIGNAL(sigSignal(float)), p_publisher_node_, SLOT(slotSignal(float)));
-
Move the subscriber and publisher class to the
QThread
that you madepublisher_class_ptr->moveToThread(&publisher_class_thread); publisher_class_thread.start();
-
Call
emit
signals and write your logic inslot
functions accordinglyemit sigSignal(5.0)
-
Summary
Qt is a tool to create custom GUI that can integrate with ROS directly. It is slightly complicated to learn and use, but it provides a very tight integration.