I'm working on a C++ Qt GUI to remote control a ROS robot. I've read that the ros::spin()
command should be issued in a seperate Thread so I basically have the usual MainWindow derived from QMainWindow
whose constructor sets up the GUI elements, makes the Subscriber Objects subscribe to their respective topic (e.g. image_transport::Subscriber
for sensor_msgs/Image
topics) and also starts another Thread. For that I have derived a "RosThread" class from QThread
that doesn't do anything but starting a ros:MultiThreadedSpinner
when RosThread::run()
is called.
As you can probably tell, I'm not exactly experienced when it comes to programming in general so my question is, wether the basic concept behind my project makes any sense to you? Especially should I leave the NodeHandle and the Subscriber Objects in the MainWindow and setup the Subscriptions from the MainWindow Constructor?
Relevant code snippets:
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), itLeft(nh), itArm(nh)
{
//subscribe to cameras
imageSubLeft = itLeft.subscribe("/camera_1/image_raw", 1000, &MainWindow::camCallbackLeft, this);
imageSubArm = itArm.subscribe("/camera_2/image_raw", 1000, &MainWindow::camCallbackArm, this);
pagestack = new QStackedWidget;
page1 = new QWidget;
grid = new QGridLayout;
page1->setLayout(grid);
pagestack->addWidget(page1);
labelLeft = new QLabel;
labelMid = new QLabel;
grid->addWidget(labelLeft, 0, 0);
grid->addWidget(labelMid, 0, 1);
this->startSpinThread(); //starts the seperate Thread where run() is executed
this->setCentralWidget(pagestack);
this->setWindowState(Qt::WindowMaximized);
this->setMinimumSize(1024, 768);
}
MainWindow::~MainWindow(){}
void MainWindow::camCallbackLeft(const sensor_msgs::Image::ConstPtr &msg){/*some code*/}
void MainWindow::camCallbackArm(const sensor_msgs::Image::ConstPtr &msg){/*some code*/}
void MainWindow::closeEvent(QCloseEvent *event){/*some code*/}
void MainWindow::startSpinThread()
{
if(rosSpin.isRunning())
{
return;
}
//rosSpin is an Object of the of QThread derived class
rosSpin.start();
}
rosthread.h:
#ifndef ROSTHREAD_H
#define ROSTHREAD_H
#include <ros/ros.h>
#include <QThread>
class RosThread : public QThread
{
Q_OBJECT
public:
RosThread();
protected:
void run();
private:
ros::MultiThreadedSpinner spinner;
};
#endif // ROSTHREAD_H
rosthread.cpp:
#include "rosthread.h"
RosThread::RosThread()
{
}
void RosThread::run() {
spinner.spin();
}
main.cpp:
#include "mainwindow.h"
#include <QApplication>
#include <ros/ros.h>
int main(int argc, char **argv)
{
ros::init(argc, argv, "gui_node");
QApplication app (argc, argv);
MainWindow *win = new MainWindow();
win->show();
return app.exec();
}
Actually, QThread is not intended to be used this way. Take a look at this blog article and this example.
But anyway, I would suggest the standard C++ threads.
Add std::unique_ptr<std::thread> thread;
to your MainWindow class instead of the RosThread
object.
To start a thread, use thread.reset(new std::thread([](){ static ros::MultiThreadedSpinner spinner; spinner.spin(); });
. The smart pointer std::unique_ptr
will automatically delete the thread object although you shouldn't forget to use std::thread::join()
or std::thread::detach()
before the resetting/destroying your std::unique_ptr
object.
Another solution would be to put the ros::MultiThreadedSpinner
object into your MainWindow class and create a std::thread
using thread.reset(new std::thread(&ros::MultiThreadedSpinner::spin, spinner));
.
In my opinion, you should put you NodeHandle and Subscriber objects into another class and use an object of this class as a member of MainWindow if they don't belong directly to MainWindow.