rqt custum pluginの作り方 〜単独実行を添えて〜
ROSを使った開発をしていると、しばしばrqt_***
などのGUIソフトウェアを利用することがある。
しかし、いろいろROSに慣れてくると自分の自分のための自分だけのGUIを作りたくなってくる。
これはそういった思いからrqt
プラグインの使い方を調べたメモ書きである。
開発環境
- Ubuntu 18.04 LTS
- ROS melodic
よくあるrqt
アプリの構成
rqt
を使用したアプリはrqt_gui
をメインウィンドウとしてプラグインを読み込むことでGUIを構成しています。
なので、自分のためのGUIを作る方法はプラグインを作成することにあります。
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.xml
にscripts
フォルダ内に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
として保存した。
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を選択します
ここでBase class name
は元となるWidgetを選択します。今回はQLCDNumber
です
Promoted class name
は格上げ後のクラス名なので好きな名前を設定してください
最後にHeader file
ですがここは注意してください
Pythonで作る場合は<package name>.<module name>
になります
例えば、パッケージ名: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
の中に現れます。
それを選択して画面上に現れればプラグイン作成完了です。
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