ROS2とQt5を使って自作UIを作ろう！！(1)

# はじめに
ロボコンをしていると、強豪校は自作のコントロールUIを作ったりしているのをよく目にします。
今回私がUIを書こうと思ったのは、それが単純にかっこいいと思ったからです。しかし、いざ書こうとすると、どのように書いたらいいかわかんなくなると思います。そんな人のためにこの記事を書きました。少しでも役に立つと幸いです。思ったより長くなりそうなのでpartを分けます。今回はROS2とQt５の連携の仕方です。
# 今回の学ロボで作ったUIの機能(ざっくり)
今回のUIに求めた機能は以下のようになっています。
* ロボットに乗せてあるカメラの画像の表示
* ボタンによってロボットに司令を送信する
* ロボットが今どこにいるのかの可視化
* ロボットの状態の表示
完成品はもう少しタイマー等の機能が増えて以下のようになりました。
![IMG_8558.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531353/1104d68c-f8b1-fdee-bd7e-3045a1123874.jpeg)
この記事ではこのUIで使った基本的な部分のみ抜粋して解説していきます。
コードの全体は[github](https://github.com/T-matumoto-matumi/test_gui)に上げておきます。

# ROS2とQt５の連携
最初の一歩はROS2とQt5の連携です。ボタンを押したら、ボタンを押した回数をpubするUIをつくります。実際に動かすと以下のようになります。
![IMG_8185.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531353/87b67777-6cbc-9f56-c138-3ab3094eebce.jpeg)


実際のコードは以下のようです。
ui_test.pyは以下のようになっておりこのコードはボタンの配置とかを指定しています。
```
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'ui_test.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
#
# WARNING! All changes made in this file will be lost!


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(400, 300)
        self.horizontalLayout = QtWidgets.QHBoxLayout(Dialog)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label = QtWidgets.QLabel(Dialog)
        self.label.setObjectName("label")
        self.horizontalLayout.addWidget(self.label)
        self.pushButton = QtWidgets.QPushButton(Dialog)
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout.addWidget(self.pushButton)

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
        self.label.setText(_translate("Dialog", "TextLabel"))
        self.pushButton.setText(_translate("Dialog", "PushButton"))
```
ROSとかの処理を書いている方（上のui_test.pyをimportして使ってます）
```
import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtGui import QPixmap,QIcon
from ui_test import *

import rclpy
from rclpy.node import Node
from std_msgs.msg import Int32

class MyForm(QDialog):
    def __init__(self,parent=None):
        super(MyForm,self).__init__(parent)
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        self.count =0
        self.ui.pushButton.clicked.connect(self.button_update)
        self.show()
        
        rclpy.init(args=None)
        self.node = Node('button_main')
        self.pub = self.node.create_publisher(Int32,'button_topic',10)
        
    def __del__(self):
        self.node.destroy_node()    
        
    def pub_int32(self,num: int):
        msg = Int32()
        msg.data=num
        self.pub.publish(msg)
        
         
    def button_update(self):
        self.count +=1
        self.pub_int32(self.count)
        print(f'{self.count}回目のボタンが押されました\n')
        
        

        

def main():
    app = QApplication(sys.argv)
    window = MyForm()
    window.show()
    sys.exit(app.exec_())

if __name__=="__main__":
    main()
```
## 解説
Qtの解説は長くなるので今回はrosに関するところを説明します。
* コンストラクタでnodeを初期化してインスタンス化しています。
その中にpablisherを宣言しています。
```
        rclpy.init(args=None)
        self.node = Node('button_main')
        self.pub = self.node.create_publisher(Int32,'button_topic',10)
```
* デイストラクタではnodeを破棄しています。
```
def __del__(self):
        self.node.destroy_node() 
```
* ボタンが押されたときのcallbackを宣言
```
self.ui.pushButton.clicked.connect(self.button_update)
```

* ボタンが押されたときのcallback関数でbutton_updateがあります。
この関数では押された回数を数えてそれをpublishしています。
```
def button_update(self):
        self.count +=1
        self.pub_int32(self.count)
        print(f'{self.count}回目のボタンが押されました\n')
```
# 終わりに
今回は主にros2側を解説しました。
この記事を書いていてQtの説明を入れるのが難しかったのでまたの機会にQtだけの記事も書いたほうがいいと感じました。