(Ubuntu)Qt下的ROS编程—在模板上加功能+注释+源码
在上篇介绍了如何创建Qt下ROS图形化GUI工程,创建好之后他会默认给我们一个模板,就在上篇博客中最后有截图到。
接下来就在工程模板的基础上添加一个订阅者Subscriber,并且画好Ui,实现图形化的Topic发布与订阅。
工程目录如下,我会按照工程目录给出源码和注释。
main_window.cpp:
/**
* @file /src/main_window.cpp
*
* @brief Implementation for the qt gui.
*
* @date February 2011
**/
/*****************************************************************************
** Includes
*****************************************************************************/
#include <QtGui>
#include <QMessageBox>
#include <iostream>
#include "../include/testgui/main_window.hpp"
/*****************************************************************************
** Namespaces
*****************************************************************************/
namespace testgui {
using namespace Qt;
/*****************************************************************************
** Implementation [MainWindow]
*****************************************************************************/
MainWindow::MainWindow(int argc, char** argv, QWidget *parent)
: QMainWindow(parent)
, qnode(argc,argv)
{
ui.setupUi(this); // Calling this incidentally connects all ui's triggers to on_...() callbacks in this class.
//QAPP是应用程序的全局变量
QObject::connect(ui.actionAbout_Qt, SIGNAL(triggered(bool)),qApp, SLOT(aboutQt())); // qApp is a global variable for the application
ReadSettings();//执行配置,用于设置初始状态配置
setWindowIcon(QIcon(":/images/icon.png"));//加载软件图标
//选项卡控件,在这里把选项设置在第一个上,此信号和槽机制用于向GUI发出关机信号(对RosLaunch有用)
ui.tab_manager->setCurrentIndex(0); // ensure the first tab is showing - qt-designer should have this already hardwired, but often loses it (settings?).
QObject::connect(&qnode, SIGNAL(rosShutdown()), this, SLOT(close()));
/*********************
** Logging 数据文本框1发布者
**********************/
ui.view_logging->setModel(qnode.loggingModel());//通过调用loggingModel这个函数,返回的是已经在qnode对象用QStringListModel包装好的数据正好是参数需要,然后可以直接设置到文本框中显示
//自定义信号和槽,用于重新调整滚动条
QObject::connect(&qnode, SIGNAL(loggingUpdated()), this, SLOT(updateLoggingView()));
/*********************
** add 数据文本框2订阅者
**********************/
//通过调用这个loggingModel_sub函数,返回的是已经在qnode对象用QString包装好的数据,然后可以直接设置到文本框中显示
ui.view_logging_sub->setModel(qnode.loggingModel_sub()); //add
//自定义信号和槽,用于重新调整滚动条
QObject::connect(&qnode,SIGNAL(loggingUpdated_sub()),this,SLOT(updateLoggingView_sub())); //add
/*********************
** Auto Start 启动时记住设置控件init
**********************/
if ( ui.checkbox_remember_settings->isChecked() ) {
on_button_connect_clicked(true);
}
}
MainWindow::~MainWindow() {}
/*****************************************************************************
** Implementation [Slots]
*****************************************************************************/
//弹出提示框
void MainWindow::showNoMasterMessage() {
QMessageBox msgBox;
msgBox.setText("Couldn't find the ros master.");
msgBox.exec();
close();
}
/*
* These triggers whenever the button is clicked, regardless of whether it
* is already checked or not.
*/
//点击连接按钮
void MainWindow::on_button_connect_clicked(bool check ) {
if ( ui.checkbox_use_environment->isChecked() ) {//if选中单选框(使用环境变量)
if ( !qnode.init() ) {//直接构造空参失败,就提示一个框
showNoMasterMessage();
} else {//否则就开始读取并且把按钮设置不可选
ui.button_connect->setEnabled(false);
}
} else {//如果没有选中单选框(不使用环境变量),就获取文本框填的URL地址和ip,如果失败就弹窗框
if ( ! qnode.init(ui.line_edit_master->text().toStdString(),
ui.line_edit_host->text().toStdString()) ) {
showNoMasterMessage();
} else {//如果ok就把东西设置成不可操控
ui.button_connect->setEnabled(false);
ui.line_edit_master->setReadOnly(true);
ui.line_edit_host->setReadOnly(true);
ui.line_edit_topic->setReadOnly(true);
}
}
}
//"是否使用环境变量"单选框状态
void MainWindow::on_checkbox_use_environment_stateChanged(int state) {
bool enabled;
if ( state == 0 ) {
enabled = true;
} else {
enabled = false;
}
ui.line_edit_master->setEnabled(enabled);
ui.line_edit_host->setEnabled(enabled);
//ui.line_edit_topic->setEnabled(enabled);
}
/*****************************************************************************
** Implemenation [Slots][manually connected] 两个文本数据跟新的槽函数
*****************************************************************************/
/**
* This function is signalled by the underlying model. When the model changes,
* this will drop the cursor down to the last line in the QListview to ensure
* the user can always see the latest log message.
* *此函数由基础模型发出信号。当模型改变时,
*这将把光标放到qlistView的最后一行,以确保
*用户总是可以看到最新的日志消息。
*/
void MainWindow::updateLoggingView() {
ui.view_logging->scrollToBottom();//用于调整滚动条到底部
}
void MainWindow :: updateLoggingView_sub(){// add
ui.view_logging_sub->scrollToBottom();//用于调整滚动条到底部
}
/*****************************************************************************
** Implementation [Menu]菜单栏显示文字
*****************************************************************************/
void MainWindow::on_actionAbout_triggered() {
QMessageBox::about(this, tr("About ..."),tr("<h2>PACKAGE_NAME Test Program 0.10</h2><p>Copyright Yujin Robot</p><p>This package needs an about description.</p>"));
}
/*****************************************************************************
** Implementation [Configuration] 执行配置,用于设置初始状态配置
*****************************************************************************/
void MainWindow::ReadSettings() {
QSettings settings("Qt-Ros Package", "testgui");
restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("windowState").toByteArray());
QString master_url = settings.value("master_url",QString("http://192.168.1.2:11311/")).toString();
QString host_url = settings.value("host_url", QString("192.168.1.3")).toString();
//QString topic_name = settings.value("topic_name", QString("/chatter")).toString();
ui.line_edit_master->setText(master_url);
ui.line_edit_host->setText(host_url);
//ui.line_edit_topic->setText(topic_name);
bool remember = settings.value("remember_settings", false).toBool();
ui.checkbox_remember_settings->setChecked(remember);
bool checked = settings.value("use_environment_variables", false).toBool();
ui.checkbox_use_environment->setChecked(checked);
if ( checked ) {
ui.line_edit_master->setEnabled(false);
ui.line_edit_host->setEnabled(false);
//ui.line_edit_topic->setEnabled(false);
}
}
void MainWindow::WriteSettings() {
QSettings settings("Qt-Ros Package", "testgui");
settings.setValue("master_url",ui.line_edit_master->text());
settings.setValue("host_url",ui.line_edit_host->text());
//settings.setValue("topic_name",ui.line_edit_topic->text());
settings.setValue("use_environment_variables",QVariant(ui.checkbox_use_environment->isChecked()));
settings.setValue("geometry", saveGeometry());
settings.setValue("windowState", saveState());
settings.setValue("remember_settings",QVariant(ui.checkbox_remember_settings->isChecked()));
}
void MainWindow::closeEvent(QCloseEvent *event)
{
WriteSettings();
QMainWindow::closeEvent(event);
}
} // namespace testgui
main_window.hpp:
/**
* @file /include/testgui/main_window.hpp
*
* @brief Qt based gui for testgui.
*
* @date November 2010
**/
#ifndef testgui_MAIN_WINDOW_H
#define testgui_MAIN_WINDOW_H
/*****************************************************************************
** Includes
*****************************************************************************/
#include <QtGui/QMainWindow>
#include "ui_main_window.h"
#include "qnode.hpp"
/*****************************************************************************
** Namespace
*****************************************************************************/
namespace testgui {
/*****************************************************************************
** Interface [MainWindow]
*****************************************************************************/
/**
* @brief Qt central, all operations relating to the view part here.
*/
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(int argc, char** argv, QWidget *parent = 0);
~MainWindow();
void ReadSettings(); // Load up qt program settings at startup
void WriteSettings(); // Save qt program settings when closing
void closeEvent(QCloseEvent *event); // Overloaded function
void showNoMasterMessage();
public Q_SLOTS:
/******************************************
** Auto-connections (connectSlotsByName())
*******************************************/
void on_actionAbout_triggered();
void on_button_connect_clicked(bool check );
void on_checkbox_use_environment_stateChanged(int state);
/******************************************
** Manual connections
*******************************************/
void updateLoggingView(); // no idea why this can't connect automatically
void updateLoggingView_sub(); //add
private:
Ui::MainWindowDesign ui;
QNode qnode;
};
} // namespace testgui
#endif // testgui_MAIN_WINDOW_H
qnode.cpp:
/**
* @file /src/qnode.cpp
*
* @brief Ros communication central!
*
* @date February 2011
**/
/*****************************************************************************
** Includes
*****************************************************************************/
#include <ros/ros.h>
#include <ros/network.h>
#include <string>
#include <std_msgs/String.h>
#include <sstream>
#include "../include/testgui/qnode.hpp"
/*****************************************************************************
** Namespaces
*****************************************************************************/
namespace testgui {
/*****************************************************************************
** Implementation
*****************************************************************************/
QNode::QNode(int argc, char** argv ) :
init_argc(argc),
init_argv(argv)
{}
QNode::~QNode() {
if(ros::isStarted()) {
ros::shutdown(); // explicitly needed since we use ros::start();
ros::waitForShutdown();
}
wait();
}
//这个构造函数是使用环境变量执行的构造函数
bool QNode::init() {
ros::init(init_argc,init_argv,"testgui");//初始化ros节点
if ( ! ros::master::check() ) {//检查master是否开启
return false;
}
// 因为我们的nodehandle超出了范围,所以显式需要。
ros::start(); // explicitly needed since our nodehandle is going out of scope.
ros::NodeHandle n;
// Add your ros communications here.在此处添加您的ROS通信。
chatter_publisher = n.advertise<std_msgs::String>("chatter", 1000);
chatter_subscriber = n.subscribe("chatter",1000,&QNode::Callback,this);//add
start();//开启此线程,一边发布topic信息一边订阅topic消息
return true;
}
//这个构造函数不使用环境变量
bool QNode::init(const std::string &master_url, const std::string &host_url) //Master的URL地址和IP地址
{
std::map<std::string,std::string> remappings;
remappings["__master"] = master_url;//给定Master的URL地址和IP地址,
remappings["__hostname"] = host_url;
ros::init(remappings,"testgui");//
if ( ! ros::master::check() ) {
return false;
}
ros::start(); // 因为我们的nodehandle超出了范围,所以显式需要。
ros::NodeHandle n;
// Add your ros communications here.
chatter_publisher = n.advertise<std_msgs::String>("chatter", 1000);
chatter_subscriber = n.subscribe("chatter",1000,&QNode::Callback,this); //add
start();//开启此线程
return true;
}
void QNode::run() {
ros::Rate loop_rate(10);
int count = 0;
while ( ros::ok() ) {
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
chatter_publisher.publish(msg);//
log(Info,std::string("I sent: ")+msg.data);//调用日志信息函数,第一个参数时信息类型,并将发布的数据组织成字符串
ros::spinOnce();
loop_rate.sleep();
++count;
}
std::cout << "Ros shutdown, proceeding to close the gui." << std::endl;
//发送此信号,至此说明程序异常或者执行完毕
Q_EMIT rosShutdown(); // used to signal the gui for a shutdown (useful to roslaunch) 用于向GUI发出关机信号(对RosLaunch有用)
}
//日志信息函数
void QNode::log( const LogLevel &level, const std::string &msg) {
logging_model.insertRows(logging_model.rowCount(),1);
std::stringstream logging_model_msg;
switch ( level ) {
case(Debug) : {
ROS_DEBUG_STREAM(msg);
logging_model_msg << "[DEBUG] [" << ros::Time::now() << "]: " << msg;
break;
}
case(Info) : {
ROS_INFO_STREAM(msg);
logging_model_msg << "[INFO] [" << ros::Time::now() << "]: " << msg;
break;
}
case(Warn) : {
ROS_WARN_STREAM(msg);
logging_model_msg << "[INFO] [" << ros::Time::now() << "]: " << msg;
break;
}
case(Error) : {
ROS_ERROR_STREAM(msg);
logging_model_msg << "[ERROR] [" << ros::Time::now() << "]: " << msg;
break;
}
case(Fatal) : {
ROS_FATAL_STREAM(msg);
logging_model_msg << "[FATAL] [" << ros::Time::now() << "]: " << msg;
break;
}
}
QVariant new_row(QString(logging_model_msg.str().c_str()));
/*
开始包装数据到QStringListModel类中,随后通过MainWIndow进行函数的调用,在函数中将返回这个包装好的数据
*/
logging_model.setData(logging_model.index(logging_model.rowCount()-1),new_row);
Q_EMIT loggingUpdated(); // used to readjust the scrollbar用于重新调整滚动条至底层
}
void QNode::log_sub(const LogLevel&level,const std::string&msg){// add
logging_model_sub.insertRows(logging_model_sub.rowCount(),1);
std::stringstream logging_model_msg;
switch (level)
{
case(Debug):{
ROS_DEBUG_STREAM(msg);
logging_model_msg <<"[DEBUG] ["<< ros :: Time :: now()<<"]:"<< msg;
break;
}
case(Info):{
ROS_INFO_STREAM(msg);
logging_model_msg <<"[INFO] ["<< ros :: Time :: now()<<"]:"<< msg;
break;
}
case(Warn):{
ROS_WARN_STREAM(msg);
logging_model_msg <<"[INFO] ["<< ros :: Time :: now()<<"]:"<< msg;
break;
}
case(Error):{
ROS_ERROR_STREAM(msg);
logging_model_msg <<"[ERROR] ["<< ros :: Time :: now()<<"]:"<< msg;
break;
}
case(Fatal):{
ROS_FATAL_STREAM(msg);
logging_model_msg <<"[FATAL] ["<< ros :: Time :: now()<<"]:"<< msg;
break;
}
}
QVariant new_row(QString(logging_model_msg.str().c_str()));
/*
开始包装数据到QStringListModel类中,随后通过MainWIndow进行函数的调用,在函数中将返回这个包装好的数据
*/
logging_model_sub.setData(logging_model_sub.index(logging_model_sub.rowCount()-1),new_row);
Q_EMIT loggingUpdated_sub(); //用于重新调整滚动条
}
//订阅者的回调函数:如果有发布的数据,就订阅并调用log_sub日志函数
void QNode::Callback(const std_msgs::StringConstPtr &submsg)// add
{
log_sub(Info,std::string(" Success sub : ")+ submsg-> data.c_str());
}
} // namespace testgui
qnode.hpp:
/**
* @file /include/testgui/qnode.hpp
*
* @brief Communications central!
*
* @date February 2011
**/
/*****************************************************************************
** Ifdefs
*****************************************************************************/
#ifndef testgui_QNODE_HPP_
#define testgui_QNODE_HPP_
/*****************************************************************************
** Includes
*****************************************************************************/
// To workaround boost/qt4 problems that won't be bugfixed. Refer to
// https://bugreports.qt.io/browse/QTBUG-22829
#ifndef Q_MOC_RUN
#include <ros/ros.h>
#endif
#include <string>
#include <QThread>
#include <QStringListModel>
#include "std_msgs/String.h" //add
/*****************************************************************************
** Namespaces
*****************************************************************************/
namespace testgui {
/*****************************************************************************
** Class
*****************************************************************************/
class QNode : public QThread {
Q_OBJECT
public:
QNode(int argc, char** argv );
virtual ~QNode();
bool init();
bool init(const std::string &master_url, const std::string &host_url);
void run();
/*********************
** Logging
**********************/
enum LogLevel {
Debug,
Info,
Warn,
Error,
Fatal
};
//
QStringListModel* loggingModel() { return &logging_model; }
void log( const LogLevel &level, const std::string &msg);
QStringListModel * loggingModel_sub(){return &logging_model_sub; } //add
void log_sub(const LogLevel&level,const std :: string&msg); //add
void Callback(const std_msgs::StringConstPtr &submsg); //add
//(const std_msgs::StringConstPtr &submsg)
//自定义信号
Q_SIGNALS:
void loggingUpdated();
void rosShutdown();
void loggingUpdated_sub(); //add
private:
int init_argc;
char** init_argv;
ros::Publisher chatter_publisher;
ros::Subscriber chatter_subscriber; //add
QStringListModel logging_model;
QStringListModel logging_model_sub; //add
};
} // namespace testgui
#endif /* testgui_QNODE_HPP_ */
main.cpp:
/**
* @file /src/main.cpp
*
* @brief Qt based gui.
*
* @date November 2010
**/
/*****************************************************************************
** Includes
*****************************************************************************/
#include <QtGui>
#include <QApplication>
#include "../include/testgui/main_window.hpp"
/*****************************************************************************
** Main
*****************************************************************************/
int main(int argc, char **argv) {
/*********************
** Qt
**********************/
QApplication app(argc, argv);
testgui::MainWindow w(argc,argv);//通过改变MainWindow的构造函数,将两个参数传递过来
w.show();
app.connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
int result = app.exec();
return result;
}
CMakeLists.txt:
##############################################################################
# CMake
##############################################################################
cmake_minimum_required(VERSION 2.8.0)
project(testgui)
##############################################################################
# Catkin
##############################################################################
# qt_build provides the qt cmake glue, roscpp the comms for a default talker
find_package(catkin REQUIRED COMPONENTS qt_build roscpp std_msgs)
include_directories(${catkin_INCLUDE_DIRS})
# Use this to define what the package will export (e.g. libs, headers).
# Since the default here is to produce only a binary, we don't worry about
# exporting anything.
catkin_package()
##############################################################################
# Qt Environment
##############################################################################
# this comes from qt_build's qt-ros.cmake which is automatically
# included via the dependency call in package.xml
rosbuild_prepare_qt4(QtCore QtGui) # Add the appropriate components to the component list here
##############################################################################
# Sections
##############################################################################
file(GLOB QT_FORMS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ui/*.ui)
file(GLOB QT_RESOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} resources/*.qrc)
file(GLOB_RECURSE QT_MOC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} FOLLOW_SYMLINKS include/testgui/*.hpp)
QT4_ADD_RESOURCES(QT_RESOURCES_CPP ${QT_RESOURCES})
QT4_WRAP_UI(QT_FORMS_HPP ${QT_FORMS})
QT4_WRAP_CPP(QT_MOC_HPP ${QT_MOC})
##############################################################################
# Sources
##############################################################################
file(GLOB_RECURSE QT_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} FOLLOW_SYMLINKS src/*.cpp)
##############################################################################
# Binaries
##############################################################################
add_executable(testgui ${QT_SOURCES} ${QT_RESOURCES_CPP} ${QT_FORMS_HPP} ${QT_MOC_HPP})
target_link_libraries(testgui ${QT_LIBRARIES} ${catkin_LIBRARIES})
install(TARGETS testgui RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
ui如下图所示:
过几天会放到github上,到时候直接拿工程编译就好了。
在我电脑上是可以运行,如果有啥不会的,或者创建失败的可以在下面留言。
有错误还请大家指出,可以互相学习,多多进步。