2
Help us understand the problem. What are the problem?

posted at

updated at

rqt custum pluginの作り方 〜単独実行を添えて〜

rqt custum pluginの作り方 〜単独実行を添えて〜

ROSを使った開発をしていると、しばしばrqt_***などのGUIソフトウェアを利用することがある。

しかし、いろいろROSに慣れてくると自分の自分のための自分だけのGUIを作りたくなってくる。
これはそういった思いからrqtプラグインの使い方を調べたメモ書きである。

開発環境

  • Ubuntu 18.04 LTS
  • ROS melodic

よくあるrqtアプリの構成

rqtを使用したアプリはrqt_guiをメインウィンドウとしてプラグインを読み込むことでGUIを構成しています。
なので、自分のためのGUIを作る方法はプラグインを作成することにあります。

rqt.drawio.png

rqt plugin の作り方

rqtのインストール

兎にも角にもrqtが必要なのでインストールします。

$ sudo apt install ros-melodic-rqt-gui ros-melodic-rqt-gui-py 
# ほとんどの人はROSをインストールした段階でインストールされてる

rqt pluginパッケージを作成する

プラグインを作成するためのパッケージを準備します。

$ mkdir -p catkin_ws/src
$ cd catkin_ws/src
# catkin-toolsを使用した場合
$ catkin create pkg my_rqt_plugin --catkin-deps rospy rqt_gui rqt_gui_py
# catkin_create_pkgを使用した場合
$ catkin_create_pkg my_rqt_plugin rospy rqt_gui rqt_gui_py

この時点で以下のようなフォルダ構成になっています

catkin_ws/
└── src/
    └── my_rqt_plugin/
        ├── src/
        ├── CMakeLists.txt
        └── package.xml

manifest.xml,plugin.xmlを作成します

作成するプラグインの基本情報やフォルダ構成を記述します。

<!-- manifest.xml -->

<package>
  <description brief="my_rqt_plugin">
    my_rqt_plugin
  </description>
  <author> *** your account name *** </author>
  <license> *** licence *** </license>
  <review status="unreviewed" notes=""/>
  <url> *** package wiki url *** </url>
  <depend package="rospy"/>
  <depend package="rqt_gui"/>
  <depend package="rqt_gui_py"/>  
  <export>
   <!-- あとで作成するplugin.xmlのパスを指定する -->
    <rqt_gui plugin="${prefix}/plugin.xml"/>
  </export>
</package>
<!-- plugin.xml -->

<!-- プラグインスクリプトが格納されているパスを指定する -->
<library path="scripts">
  <!-- あとで作成するプラグインスクリプトをインポートする際のクラス名です -->
  <!-- 今は仮でこのように設定しておきます -->
  <class name="my_rqt_plugin" type="my_rqt_plugin.my_rqt_plugin.MyRqtPlugin" base_class_type="rqt_gui_py::Plugin">
    <description>
      my rqt plugin
    </description>
    <qtgui>
      <label>my_rqt_plugin</label>
      <icon type="theme">applications-other</icon>
      <statustip>my_rqt_plugin</statustip>
    </qtgui>
  </class>
</library>

この時点で以下のようなフォルダ構成になります。

catkin_ws/
└── src/
    └── my_rqt_plugin/
        ├── src/
        ├── CMakeLists.txt
        ├── package.xml
        ├── manifest.xml  # <- ここに作成する
        └── plugin.xml    # <- ここに作成する

プラグインスクリプトを作成する

plugin.xmlscriptsフォルダ内にmy_rqt_plugin.my_rqt_plugin.MyRqtPluginなるプラグインを作成したと記述したので
以下のようなファイルを作成する。

catkin_ws/
└── src/
    └── my_rqt_plugin/
        ├── src/
        ├── CMakeLists.txt
        ├── package.xml
        ├── manifest.xml
        └── plugin.xml
        └── scripts/                  # <- フォルダを作成
            └── my_rqt_plugin/        # <- フォルダを作成
                ├── __init__.py       # <- 空のファイルで良い
                └── my_rqt_plugin.py  # <- ファイルを作成する

UIファイルを作成する

ここでROSではあまり馴染みがないがQt Designerで生成する.uiを作成します。

※Qt自体に馴染みがない方はこちらのチュートリアルとある程度、慣れておこう
Qt をはじめよう! 第13回: GUI デザイナを使おう

今回はQt Designerを使用して以下のようなGUIをmy_rqt_gui.uiとして保存した。
image.png

catkin_ws/
└── src/
    └── my_rqt_plugin/
        ├── src/
        ├── CMakeLists.txt
        ├── package.xml
        ├── manifest.xml
        ├── plugin.xml
        └── scripts/        
            └── my_rqt_plugin/
                ├── __init__.py
                ├── my_rqt_plugin.py
                └── my_rqt_gui.ui     # <- ファイルを作成する

プラグインスクリプトを記述する

詳細はスクリプト上に記述します。

.uiファイルを読み込み、親オブジェクトに追加するまでの処理を記述する。

# my_rqt_plugin.py

#! /usr/bin/env python2
# -*- coding: utf-8 -*-

import roslib
roslib.load_manifest('my_rqt_plugin')
import os
import rospy

from qt_gui.plugin import Plugin
from python_qt_binding import loadUi
from python_qt_binding.QtWidgets import QWidget  # ROSバージョンによってインポート時のパスが違う


class MyRqtPlugin(Plugin):

  def __init__(self, context):

    # Pluginクラスを初期化する
    super(MyRqtPlugin, self).__init__(context)

    self.setObjectName('MyRqtPlugin')

    self._widget = QWidget()

    # .ui ファイルを取得する
    ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'my_rqt_gui.ui')
    # .ui ファイルを読み込み、_widget配下にする
    loadUi(ui_file, self._widget)

    self._widget.setObjectName('MyRqtPluginUi')

    # ウィンドウタイトルを設定する
    if context.serial_number() > 1:
      self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))

    # Widgetを親のオブジェクトに追加する
    context.add_widget(self._widget)    

    # Pluginとしてのインターフェースを記述する
    def shutdown_plugin(self):
      pass

    def save_settings(self, plugin_settings, instance_settings):
      pass

    def restore_settings(self, plugin_settings, instance_settings):
      pass

ここまででプラグインの作成方法は終了です。

カスタムウィジェットの作り方

今回はQt DesignerのPromoteを使ってWidgetを格上げします
格上げしたいWidgetを選択し、右クリックからPromoteを選択します

image.png

ここでBase class nameは元となるWidgetを選択します。今回はQLCDNumberです
Promoted class nameは格上げ後のクラス名なので好きな名前を設定してください
最後にHeader fileですがここは注意してください
Pythonで作る場合は<package name>.<module name>になります

image.png

例えば、パッケージ名:my_rqt_plugin内にlcdnumber.pyというモジュールを作成した場合、my_rqt_plugin.lcdnumberになります

catkin_ws/
└── src/
    └── my_rqt_plugin/
        ├── src/
        ├── CMakeLists.txt
        ├── package.xml
        ├── manifest.xml
        ├── plugin.xml
        └── scripts/        
            └── my_rqt_plugin/
                ├── __init__.py
                ├── my_rqt_plugin.py
                ├── my_rqt_gui.ui
                └── lcdnumber.py     # <- ファイルを作成する

参考として、カスタムウィジェットのクラスの書き方を載せます。

#! /usr/bin/env python2
# -*- coding: utf-8 -*-

import rospy
from std_msgs.msg import Int32
from python_qt_binding.QtWidgets import QLCDNumber

class LCDNumber(QLCDNumber):

  def __init__(self, parent=None):

    super(LCDNumber, self).__init__(parent)

トピックとの連動方法

カスタムプラグイン内ではrospy.init_nodeをする必要はありません。rqt_guiがやってくれます。
なので、通常のROSノードと同じように設定を行います

import roslib
roslib.load_manifest('my_rqt_plugin')
import os
import rospy

from qt_gui.plugin import Plugin
from python_qt_binding import loadUi
from python_qt_binding.QtWidgets import QWidget
from std_msgs.msg import Int32

class MyRqtPlugin(Plugin):

  def __init__(self, context):

    # Pluginクラスを初期化する
    super(MyRqtPlugin, self).__init__(context)    
    self.setObjectName('MyRqtPlugin')
    self._widget = QWidget()

    # .ui ファイルを取得する
    ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'my_rqt_gui.ui')
    # .ui ファイルを読み込み、_widget配下にする    
    loadUi(ui_file, self._widget, {'LCDNumber': LCDNumber})

    self._widget.setObjectName('MyRqtPluginUi')
    self._widget.pushButton.clicked.connect(self.publish)

    # ウィンドウタイトルを設定する
    if context.serial_number() > 1:
      self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))

    # Widgetを親のオブジェクトに追加する
    context.add_widget(self._widget)

    # 配信設定
    self.__pub = rospy.Publisher('rqt_test_pub', Int32, queue_size=10)
    self.__sub = rospy.Subscriber('rqt_test_sub', Int32, self.callback)

  def callback(self, data):
    print(data)

シグナル・スロットの記述方法

ここはPyQtと同じなのでサンプルのみ記述します

既存のシグナルスロットを接続する

# ボタンクリックにpublish関数を連動させる
self._widget.pushButton.clicked.connect(self.publish)

カスタムシグナル・スロットを記述する

from python_qt_binding.QtCore import Signal, Slot

class MyRqtPlugin(Plugin):
  # シグナルオブジェクトを準備する
  addvalue = Signal(int)

  def __init__(self):
    # シグナルとスレッドを接続する
    self.addvalue.connect(self.update_value)

  def test(self):
    # シグナルを発行する
    self.addvalue.emit(1)

  # スロットを宣言する
  @Slot(int)
  def update_value(self, value):
    print(value)

自作プラグインの動作確認

上記までのプラグイン作成を終えた後、プラグインの動作確認を行う。
まずはパッケージをビルドし、環境変数を通す。

$ cd catkin_ws/
$ catkin build my_rqt_plugin
$ source devel/setup.bash

rqt_guiを起動し、自作プラグインが認識されていることを確認する

$ rosru rqt_gui rqt_gui

認識された場合はPluginsの中に現れます。
それを選択して画面上に現れればプラグイン作成完了です。

image.png

image.png

rqtプラグインの単独実行

ここまでで作成したプラグインですが、いちいちrqt_guiを起動してからプラグインを呼び出すのが面倒です。
他のGUIアプリのようにrosrun rqt_*** rqt_***のように実行したいですよね

そこで一つファイルを作成します。
※名前は何でもいいです

catkin_ws/
└── src/
    └── my_rqt_plugin/
        ├── src/
        ├── CMakeLists.txt
        ├── package.xml
        ├── manifest.xml
        ├── plugin.xml
        └── scripts/        
            ├── my_rqt_plugin/
            │   ├── __init__.py
            │   ├── my_rqt_plugin.py
            │   ├── my_rqt_gui.ui
            │   └── lcdnumber.py
            └── my_rqt_gui        # <- ファイルを作成する
# my_rqt_gui

#! /usr/bin/env python2
# -*- coding: utf-8 -*-

import sys

from rqt_gui.main import Main

# メインウィンドウを作成する
main = Main()
# メインウィンドウ内にプラグインをスタンドアロンで呼び出す
sys.exit(main.main(sys.argv, standalone='my_rqt_plugin.my_rqt_plugin.MyRqtPlugin'))

最後にこのファイルに実行権限を与えて実行します。
GUIが表示されるはずです。

chmod +x catkin_ws/src/my_rqt_plugin/scripts/my_rqt_gui
rosrun my_rqt_plugin my_rqt_gui

image.png

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
2
Help us understand the problem. What are the problem?