6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ROS講座117 QtQuick(qml)を使う

Last updated at Posted at 2020-07-04

環境

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

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

インストールについてはROS講座02 インストールを参照してください。
またこの記事のプログラムはgithubにアップロードされています。ROS講座11 gitリポジトリを参照してください。

概要

これまでQtを使ってGUIを作る方法を解説してきましたが、Qtの記述は煩雑で、簡単に変更をする必要なあるUIを作るには不向きです。このためにQtQuickという仕組みがあります。QtQuickではGUIの設定が書かれたqmlファイルを読み込んでGUIを生成します。
今回はこれを使ってROSの通信をしてみます。

QtQuickの基本的な使い方

依存パッケージのインストール

sudo apt-get install qtquickcontrols2-5-dev 
sudo apt-get install qml-module-qtqml-models2 
sudo apt-get install qml-module-qtquick-window2
sudo apt-get install qml-module-qtquick-controls

ソースコード

プログラム本体

qml_lecture/src/qml_basic.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char** argv)
{
  // Init Qt
  QGuiApplication app(argc, argv);

  if (argc < 2)
  {
    printf("qml filename is ewquired.\n");
    return 0;
  }

  QQmlApplicationEngine engine(&app);
  engine.load(QUrl(argv[1]));

  return app.exec();
}

QQmlApplicationEngine engine(&app)でqmlのエンジンをインスタンス化して、engine.load(QUrl("*****"))でqmlスクリプトを読み込みます。QUrlの中はファイルの相対or絶対パスを書きます。

qml

qml_lecture/resources/basic.qml
import QtQuick 2.3
import QtQuick.Window 2.2
import QtQuick.Controls 1.2

Window {
    Column {
        Button {
            text: "Ok"
            onClicked: text_label.text = "ok"
        }
        Button {
            text: "Cancel"
            onClicked: text_label.text = "cancel"
        }
        Text {
            id: text_label
            text: "Hello World!"
        }
        Slider {
            onValueChanged : text_label.text = value
        }
    }
    visible: true
}
  • qmlではGUIの設定をします。「エレメント」と呼ばれる要素(上記ではwindow、buttonなど)を並べます。
  • WindowはGUIのウィンドウを作るエレメントです。
  • Culumnはエレメントを縦に並べるエレメントです。
  • Buttonはボタンを出現させるエレメントです。textはボタンの中に書かれる文字です。onClickedに書いた文がボタンを押したときに実行されます。
  • Textは文字を表示するエレメントです。idは各々のエレメントを識別する要素です。上記のようにidをつけると、ほかのエレメントからも(id名).(要素名)の形式でアクセスることが出来ます。
  • Sliderはスライダーを表示する要素です。onValueChangedではスライダーを動かしたときに実行する分を書けます。

ビルド

cd ~/catkin_ws
catkin build

実行

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

ターミナル1
roscore
ターミナル2
roscd qml_lecture/
rosrun qml_lecture qml_basic resources/basic.qml 

qt_qml1.gif

ROSの通信を行う

方法としては2通りあって

  • ROSの通信を行うエレメントを作成する
  • ROSの通信を行うオブジェクトを製作してプログラム本体で接続する。

手軽なので、後者を使います。

ソースコード

ros通信用object

qml_lecture/src/pubsub_object.h
#pragma once

#include <ros/ros.h>
#include <std_msgs/String.h>

#include <QObject>
#include <QVariant>
#include <QDebug>

class RosStringObject : public QObject
{
  Q_OBJECT
public:
  RosStringObject(QObject* parent = nullptr)
  {
    connect(this, &RosStringObject::publishString, this, &RosStringObject::publishStringSlot);
    string_pub_ = nh_.advertise<std_msgs::String>("chatter", 10);
    string_sub_ = nh_.subscribe("chatter", 10, &RosStringObject::stringCallback, this);
  }

signals:
  void publishString(QString s);
  void subscribeString(QString text);

private slots:
  void publishStringSlot(QString s)
  {
    std_msgs::String msg;
    msg.data = s.toStdString();
    string_pub_.publish(msg);
  }

private:
  void stringCallback(const std_msgs::String& msg)
  {
    emit subscribeString(QString(msg.data.c_str()));
  }

  ros::NodeHandle nh_;
  ros::Publisher string_pub_;
  ros::Subscriber string_sub_;
};
  • publishString()は他のエレメントからたたいてもらうためのsignalです。Qtではsignalはインターフェースの役割しか果たさないので実装は書けません。connect()publishStringSlot()をつないで、この中で実装を書きます。
  • ROSトピックを受けるとstringCallback()が呼ばれます。この中でemit subscribeString()によってsignalを発します。

プログラム本体

qml_lecture/src/qml_ros.cpp
#include <ros/ros.h>
#include <std_msgs/String.h>

#include <QCoreApplication>
#include <QObject>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QUrl>
#include <QString>
#include <QtQml>
#include <QtConcurrent/QtConcurrent>
#include <QFuture>
#include <QFutureWatcher>

#include "pubsub_object.h"

int main(int argc, char** argv)
{
  if (argc < 2)
  {
    printf("qml filename is ewquired.\n");
    return 0;
  }

  ros::init(argc, argv, "qml_ros", ros::init_options::AnonymousName);
  ros::NodeHandle nh;

  QGuiApplication app(argc, argv);
  RosStringObject ros_string(&app);

  // for ros spin()
  QFutureWatcher<void> rosThread;
  rosThread.setFuture(QtConcurrent::run(&ros::spin));
  QObject::connect(&rosThread, &QFutureWatcher<void>::finished, &app, &QCoreApplication::quit);
  QObject::connect(&app, &QCoreApplication::aboutToQuit, []() { ros::shutdown(); });

  QQmlApplicationEngine engine(&app);
  engine.rootContext()->setContextProperty("ros_string", &ros_string);
  engine.load(QUrl(argv[1]));

  return app.exec();
}
  • プログラム中央はros::spin()を実行しているものです。別スレッドで回して、Qtのプログラムが終了すると一緒に落ちるようにしています。
  • engine.rootContext()->setContextProperty()では先ほど作ったROS通信用のobjectをqtに接続しています。
    • 第1引数はオブジェクトの名前を設定します。qmlファイルからはこの名前で指定します。

qml (pub用)

qml_lecture/resources/pub.qml
import QtQuick 2.3
import QtQuick.Window 2.2
import QtQuick.Controls 1.2

Window {
    Column {
        Button {
            text: "OK"
            onClicked: ros_string.publishString("OK")
        }
        Button {
            text: "Cancel"
            onClicked: ros_string.publishString("Cancel")
        }
    }
    visible: true
}
  • ButtononClickedros_string.publishString()を記述しています。これによって上記のROS通信用objectにsignalが発行されます。

qml (sub用)

qml_lecture/resources/sub.qml
import QtQuick 2.3
import QtQuick.Window 2.2
import QtQuick.Controls 1.2

Window {
    Column {
        Text {
            id: label_text
            text: "unknown"
        }
    }
    Connections{
        target:ros_string
        onSubscribeString: label_text.text = text
    }
    visible: true
}
  • Connectionはqtプログラムの中でのconnect()に相当する処理が行えるエレメントです。
    • targetではプログラム本体で定義したobjectの名前を指定します。
    • onSubscribeStringはシグナル名です。onSubscribeStringとするとROS通信用のobjectのsubscribeStringという名前のsignalが発行されたときの動作をします。xxxYyyyという名前のシグナルならonXxxxYyyyという名前に変換します。

ビルド

cd ~/catkin_ws
catkin build

実行

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

ターミナル1
roscore
ターミナル2
roscd qml_lecture/
rosrun qml_lecture qml_ros resources/pub.qml 
ターミナル3
roscd qml_lecture/
rosrun qml_lecture qml_ros resources/sub.qml 

qt_qml2.gif

参考

ROS_QML_Example
qml入門
カスタムエレメントを作る

目次ページへのリンク

ROS講座の目次へのリンク

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?