ROS講座94 Qtでpub・subする


環境

この記事は以下の環境で動いています。

項目
CPU Core i5-8250U
Ubuntu 20.04
ROS Noetic
Qt 5.12.8

インストールについてはROS講座02 インストールを参照してください。
またこの記事のプログラムはgithubにアップロードされています。ROS講座11 gitリポジトリを参照してください。

概要

QtのGUIを使ってpublishするROSノードであるtalker、subscribeするROSノードであるlistenerを製作します。talkerではQwidgetのSIGNALを自作関数で受ける必要があります。またlistenerではqtのmainスレッド処理とrosのメインスレッド処理を両立することが必要です。特にtalkerではコーディングでは仕様によりc++ライクの別クラス実装でライブラリの宣言と実装は別ファイルにする必要があります。
冗長な書き方に見えますが両立の都合上このような書き方になります。

talkerのソースコード

ノード本体

qt_lecture/src/qt_talker.cpp
#include <ros/ros.h>

#include <QApplication>
#include <QDialog>

#include "qt_talker_class.h"

int main(int argc, char** argv){
  ros::init(argc, argv, "qt_talker");
	QApplication app(argc,argv);
	QWidget* window = new QWidget;
	MainDialog* dialog = new MainDialog(window);
	dialog->show();

	ros::Rate loop_rate(20); 
	while (ros::ok()){
		ros::spinOnce();
    app.processEvents();
		loop_rate.sleep();
  }
}

ライブラリ

qt_lecture/src/qt_talker_class.h
#ifndef Q_MOC_RUN
#include <ros/ros.h>
#endif

#include <QDialog>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>

class MainDialog : public QDialog
{
  Q_OBJECT
public:
  MainDialog(QWidget* parent);

private Q_SLOTS:
  void publishString(); 
private:
  QPushButton* setButton_;
  ros::NodeHandle nh_;
  ros::Publisher string_pub_; 
};
  • 一番大事なのはcallback関数のvoid publishString();です。これを宣言する前の行にprivate Q_SLOTS:を付けます。
  • また#include <ros/ros.h>はQtのMOCでエラーになるために#ifndef Q_MOC_RUN~#endifで囲みます。
qt_lecture/src/qt_talker_class.cpp
#include <ros/ros.h>
#include <std_msgs/String.h>

#include <QDialog>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QVBoxLayout>

#include "qt_talker_class.h"

MainDialog::MainDialog(QWidget* parent): QDialog(parent),nh_(){
  setButton_ = new QPushButton("publish");

  connect(setButton_, &QPushButton::clicked, this, &MainDialog::publishString);

  QVBoxLayout* layout = new QVBoxLayout;
  layout->addWidget(setButton_);
  setLayout(layout);

  string_pub_ = nh_.advertise<std_msgs::String>("chatter", 10);
}

void MainDialog::publishString(){
  std_msgs::String string_msg;
  string_msg.data="string";
  string_pub_.publish(string_msg);
  ROS_INFO("pub: %s", string_msg.data.c_str()); 
}

listenerのソースコード

ノード本体

qt_lecture/src/qt_listener.cpp
#include <ros/ros.h>

#include <QApplication>
#include <QDialog>

#include "qt_listener_class.h"

int main(int argc, char** argv)
{
  ros::init(argc, argv, "qt_listener");
  QApplication app(argc,argv);
  QWidget* window = new QWidget;
  MainDialog* dialog = new MainDialog(window);
  dialog->show();

  ros::Rate loop_rate(20); 
  while (ros::ok()){
    ros::spinOnce();
    app.processEvents();
    loop_rate.sleep();
  }
}

最後のループの部分がいつもと違います。普段はROSではros::spin()を入れますし、qtではapp.exec()を入れます。ただし両方の関数ともにブロッキングなのでROSとQtを共存させることはできません。そこで今回は両者ともノンブロッキングな関数を使います。

ライブラリ

qt_lecture/src/qt_listener_class.h
#ifndef Q_MOC_RUN
#include <ros/ros.h>
#include <std_msgs/String.h>
#endif

#include <QDialog>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>

class MainDialog : public QDialog
{
  Q_OBJECT
public:
  MainDialog(QWidget* parent);

private:
  QLineEdit* lineEdit_;
  ros::NodeHandle nh_;
  ros::Subscriber string_sub_;
  void stringCallback(const std_msgs::String& msg); 
};
qt_lecture/src/qt_listener_class.cpp
#include <ros/ros.h>
#include <std_msgs/String.h>
#include <functional>

#include <QDialog>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QVBoxLayout>

#include "qt_listener_class.h"

MainDialog::MainDialog(QWidget* parent): QDialog(parent),nh_(){
  lineEdit_ = new QLineEdit;

  QVBoxLayout* layout = new QVBoxLayout;
  layout->addWidget(lineEdit_);
  setLayout(layout);

  string_sub_ = nh_.subscribe("chatter", 10, &MainDialog::stringCallback, this);
  printf("register\n");
}

void MainDialog::stringCallback(const std_msgs::String& string_msg){
  QString text = QString::fromStdString(string_msg.data);
  lineEdit_->setText(text);
  ROS_INFO("sub: %s", string_msg.data.c_str());
}

クラスの中で自らのクラスのメソッドをcallbackとして登録するときに
×string_sub_ = nh_.subscribe("chatter", 10, stringCallback);
と書きたくなりますが、
string_sub_ = nh_.subscribe("chatter", 10, &MainDialog::stringCallback, this);
と書くのが正しいです。

cmake

qt_lecture/CMakeLists.txtの追加
add_executable(qt_talker src/qt_talker.cpp src/qt_talker_class.cpp)
add_executable(qt_listener src/qt_listener.cpp src/qt_listener_class.cpp)

target_link_libraries(qt_listener
  ${catkin_LIBRARIES}
  ${QT_LIBRARIES}
)
target_link_libraries(qt_touch_app
  ${catkin_LIBRARIES}
  ${QT_LIBRARIES}
)

別途Qtのビルド設定も忘れずに

ビルド

cd ~/catkin_ws
catkin build

実行

各ターミナルごとに実行前にsource ~/catkin_ws/devel/setup.bashを実行する必要があります。

1つ目のターミナル
roscore
2つ目のターミナル
rosrun qt_lecture qt_talker 
3つ目のターミナル
rosrun qt_lecture qt_listener

参考

Qtをノンブロッキングに使う

目次ページへのリンク

ROS講座の目次へのリンク