ROS講座94 Qtでpub・subする

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

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

インストールについては[ROS講座02 インストール](https://qiita.com/srs/items/e0e0a9dc3f94c2d3348e)を参照してください。
またこの記事のプログラムはgithubにアップロードされています。[ROS講座11 gitリポジトリ](https://qiita.com/srs/items/25a56a6abda41dcf396e)を参照してください。

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

# talkerのソースコード
## ノード本体
```c++: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();
  }
}
```

## ライブラリ
```c++: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`で囲みます。

```c++: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のソースコード
## ノード本体

```c++: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を共存させることはできません。そこで今回は両者ともノンブロッキングな関数を使います。

## ライブラリ

```c++: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); 
};
```

```c++: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
```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のビルド設定も忘れずに

# ビルド
```shell
cd ~/catkin_ws
catkin build
```

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

```shell:1つ目のターミナル
roscore
```

```shell:2つ目のターミナル
rosrun qt_lecture qt_talker 
```

```shell:3つ目のターミナル
rosrun qt_lecture qt_listener
```

![qt_pub_sub.gif](https://qiita-image-store.s3.amazonaws.com/0/254442/fc1d9d37-76d3-c7a4-0865-6ad5f1fec2db.gif)

# 参考
[Qtをノンブロッキングに使う](https://stackoverflow.com/questions/15202098/using-qapplication-in-a-non-blocking-way)

# 目次ページへのリンク
[ROS講座の目次へのリンク](https://qiita.com/srs/items/5f44440afea0eb616b4a)