RoboTry

ROSを使ってロボットを動かすぞ

とてもつらいので、その気持ちをROS2のrqtプラグインに込めました。

どうも つらい と雷 です。

ROS2で動くrqtのプラグインを作りました。作り方を説明します。

f:id:macakasit:20200105002250p:plain
作成したrqt_tsurai

GitHubにも公開してます。

github.com

目次

つらいときはrqtのプラグインを作ろう

つらいね

つらいけど動作環境を教えるよ

  • Ubuntu 18.04
  • ROS 2 Dashing
  • ワークスペース~/ros2_wsです
  • rqtコマンドでrqtが起動する状態です
  • Qt 5 Designer
    • rqtプラグインのUIを作るとても重要なツールです
    • 申し訳ありませんが、インストール方法を忘れました
    • つらいけどググってください
    • 参考までに、/usr/lib/qt5/bin/designerを実行してます
    • ごめんね

つらいけど空のパッケージつくるよ

まず、空のパッケージを作ります。名前はrqt_tsuraiです。

cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake rqt_tsurai  

とりあえずビルドします。

cd ~/ros2_ws
colcon build

ビルドできたらオッケー。

パッケージを参考にするのもつらいね

すでに存在するROS2 で動くrqtプラグイン(パッケージ)を参考にします。

github.com

このパッケージはROS2で動くからいいね。羨ましい。起動してみよう。

rqt

起動したら Plugins -> Logging -> Console でrqt_consoleプラグインを選択します。

f:id:macakasit:20200104230814p:plain
rqt_console。デバッグで定番のプラグインです。

このプラグインは動いていいね。

コードを書くのはつらいので、だからコーディングはつらいと思います

それでは実装していきましょう。

最終的にどんなパッケージ構成になるのか

ros2 pkg create直後はこのようになってます。

$ tree
.
├── CMakeLists.txt
├── include
│   └── rqt_tsurai
├── package.xml
└── src

これを、以下のようにしていきます。includeディレクトリは不要なので削除してください。

$ tree
.
├── CMakeLists.txt
├── package.xml
├── plugin.xml
├── resource
│   └── tsurai_widget.ui
├── scripts
│   └── rqt_tsurai
└── src
    └── rqt_tsurai
        ├── __init__.py
        ├── tsurai.py
        └── tsurai_widget.py

恐れることはありません。つらいけど順番に説明します。

UIの作成 tsurai_widget.ui

resourceディレクトリを作成します。

mkdir resource 

Qt 5 デザイナを起動します。Widgetテンプレートを選択して作成。

f:id:macakasit:20200104232532p:plain
templates/forms のWidgetを選択

こんな画面が出ます。

f:id:macakasit:20200104232708p:plain
Qt デザイナの編集画面

プロパティを編集します。

objectNameTsuraiWidgetに、windowTitleTsuraiにします。

f:id:macakasit:20200104233150p:plain
つらいプロパティ編集

名前を付けて保存します。resouceディレクトリにtsurai_widget.uiという名前で保存しましょう。

機能の実装 tsurai.py tsurai_widget.py

スクリプトファイルを作成します。つらいけどがんばりましょう。

src以下にrqt_tsuraiディレクトリを作り、その中に空のファイルを作成します。

cd src
mkdir rqt_tsurai
cd rqt_tsurai
touch __init__.py
touch tsurai.py
touch tsurai_widget.py

つぎにtsurai_widget.pyを編集します。描画機能を担当するところです。つらいです。

コピペ推奨です。

# coding: UTF-8

import os

from ament_index_python.resources import get_resource

from python_qt_binding import loadUi
from python_qt_binding.QtWidgets import QWidget
from python_qt_binding.QtGui import QPainter, QFont
from python_qt_binding.QtCore import Qt

# クラス名は参照されるので書き間違えないように
# Tusrai とか書くとつらいです
class TsuraiWidget(QWidget):

    def __init__(self):
        super(TsuraiWidget, self).__init__()

        # パッケージ名も書き間違えないように
        pkg_name = 'rqt_tsurai'
        _, package_path = get_resource('packages', pkg_name)
        # UIをロードするけどファイル名を間違えないように
        ui_file = os.path.join(
            package_path, 'share', pkg_name, 'resource', 'tsurai_widget.ui')
        loadUi(ui_file, self)

        # オブジェクト名は間違えても動く?未調査
        self.setObjectName('TsuraiWidget')


    # この関数が一定周期で呼び出される
    def paintEvent(self, event):
        painter = QPainter(self)

        # Hello world
        # ブラシ(塗りつぶし)の色を黒に
        painter.setBrush(Qt.black)
        # ペン(線描画)の色も黒に
        painter.setPen(Qt.black)
        # 背景を描く
        # self.rect()はwidgetのサイズを返すので、widget全体を埋める四角形を描画する
        # ペンとブラシの色が黒なので背景色は真っ黒
        painter.drawRect(self.rect())

        # ペン(線描画)の色を白にする
        painter.setPen(Qt.white)

        # フォントサイズを変更する
        font = painter.font()
        # つらさはできるだけ大きく表現したほうが良い
        # 周りが気づくように
        font.setPointSize(80)
        painter.setFont(font)
        
        # テキストを描画する
        x = 0 # 左端
        y = self.rect().height()*0.5 # 上下の中心
        painter.drawText(x,y, "TSU☆RA☆I")

つぎにtusrai,pyを編集します。

# coding: UTF-8

from qt_gui.plugin import Plugin
from python_qt_binding.QtCore import QTimer

from rqt_tsurai.tsurai_widget import TsuraiWidget


# クラス名は参照されるので書き間違えないこと
# Tusrai とか書くとつらいです
class Tsurai(Plugin):

    def __init__(self, context):
        super(Tsurai, self).__init__(context)
        # オブジェクト名は間違えても動く?未調査
        self.setObjectName('Tsurai')

        self._context = context

        # ここでTsuraiWdigetをセットしてつらくなろう
        self._widget = TsuraiWidget()
        if context.serial_number() > 1:
            self._widget.setWindowTitle(
                self._widget.windowTitle() + (' (%d)' % context.serial_number()))
        context.add_widget(self._widget)

        # TsuraiWidgetは一定周期で更新したいのでQTimerを使う
        self._timer = QTimer()
        self._timer.timeout.connect(self._widget.update)
        # 16 msec 周期で更新させる
        self._timer.start(16)


    def shutdown_plugin(self):
        # 終了時はタイマーを止める
        self._timer.stop()
        pass


    def save_settings(self, plugin_settings, instance_settings):
        # セーブ機能は何もしない
        # つらい気持ちをファイルに保存する機能 いる?
        pass


    def restore_settings(self, plugin_settings, instance_settings):
        # リストア機能は何もしない
        # つらい気持ちを復元してどうするの?
        pass

つらいスクリプトファイルの作成 rqt_tsurai

scriptsディレクトリを作成して、そこにrqt_tsuraiファイルを作成します。

mkdir scripts
cd scripts
touch rqt_tsurai

rqt_tsuraiを編集します。中身はPythonコードです。

#!/usr/bin/env python3

import sys

from rqt_gui.main import Main

main = Main()
sys.exit(main.main(sys.argv, standalone='rqt_tsurai.tsurai.Tsurai'))

プラグインファイルの作成 plugin.xml

準備が整ったのでplugin.xmlを作成します。

<library path="src">
  <!-- クラス名が重要なので書き間違えないように -->
  <class name="Tsurai" type="rqt_tsurai.tsurai.Tsurai" base_class_type="rqt_gui_py::Plugin">
    <description>
      <!-- プラグインの説明です。 -->
      A Python GUI plugin for displaying TSURAI
    </description>
    <qtgui>
      <group>
        <!-- プラグインのグループを設定します -->
        <!-- rqtを起動したときにPluginsから選択するアレです -->
        <!-- さすがにLoggerではないのでVisualizationにしました -->
        <!-- すでにあるVisualizationのプラグイン(例えばImage Viewを)を参照するのがおすすめ -->
        <label>Visualization</label>
        <icon type="theme">folder</icon>
        <statustip>Plugins related to visualization.</statustip>
      </group>
      <!-- プラグインのラベルです。ちゃんとTsuraiって書こうね -->
      <label>Tsurai</label>
      <!-- プラグインのアイコンです。何でもいいと思います。 -->
      <!-- Image Viewと同じにしました。 -->
      <icon type="theme">image-x-generic</icon>
      <!-- プラグイン選択時にrqtウィンドウの1番下に表示される説明文です -->
      <statustip>A Python GUI plugin for displaying TSURAI</statustip>
    </qtgui>
  </class>
</library>

CMakeLists.txt の編集

もう少しで完成です。つらいけどがんばりましょう。

パッケージがビルドできるように、CMakeLists.txtを編集します。

cmake_minimum_required(VERSION 3.5)
project(rqt_tsurai)

# Load ament and all dependencies required for this package
find_package(ament_cmake REQUIRED)
find_package(ament_cmake_python REQUIRED)

ament_python_install_package(${PROJECT_NAME}
    PACKAGE_DIR src/${PROJECT_NAME})

install(FILES plugin.xml
  DESTINATION share/${PROJECT_NAME}
)

install(DIRECTORY resource
  DESTINATION share/${PROJECT_NAME}
)

install(PROGRAMS scripts/rqt_tsurai
  DESTINATION lib/${PROJECT_NAME}
)

ament_package()

package.xmlの編集

最後にpackage.xmlを編集して、パッケージの説明や依存関係を書きましょう。

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>rqt_tsurai</name>
  <version>0.0.0</version>
  <description>rqt_tsurai provides a GUI plugin for displaying TSURAI.</description>
  <maintainer email="hoge@hoge.com">akshota</maintainer>
  <license>MIT</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <exec_depend>ament_index_python</exec_depend>
  <exec_depend version_gte="0.2.19">python_qt_binding</exec_depend>
  <exec_depend>rclpy</exec_depend>
  <exec_depend>rqt_gui</exec_depend>
  <exec_depend>rqt_gui_py</exec_depend>
  <exec_depend>rqt_py_common</exec_depend>

  <export>
    <!-- <architecture_independent/> -->
    <rqt_gui plugin="${prefix}/plugin.xml"/>
    <build_type>ament_cmake</build_type>
  </export>
</package>

つらいパッケージのビルドと実行

つらさを乗り越えてパッケージができました。ビルドしましょう

cd ~ros2_ws
colcon build

セットアップファイルを読み込みます。読み込むファイルは使用しているシェルに合わせてください。

source ./install/setup.zsh

rqtを起動してTsuraiプラグインを読み込みます。rqtがプラグイン参照できるようにオプションを付けます。

# 新たにプラグインを作成した後は、オプションを付けてrqtを起動
rqt --force-discover

Plugins -> Visualization -> Tsuraiを選択します。Widgetが表示されたら成功です。

f:id:macakasit:20200105002250p:plain
TSU☆RA☆I

おわりに

rqtプラグインのチカラはこんなもんじゃありません。 ボタンとかスライダとか、UIを鍛えれば更に強くなれます。弱いのは僕です。

つらいけど、この記事をまとめます。