2
3

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 5 years have passed since last update.

TurtleSim(ROSシミュレータ)のWebGUIをbootstrapで制作する

Last updated at Posted at 2019-11-17

概要

「bootstrapの使い方」〜「ROS環境への実装」が主な内容になります。

したがって、本記事は以下のものを作成して実装したらゴールとします。

GUI機能:

  1. [前進]ボタンを押すと、TurtleSimの亀が前進する。
  2. [左回転]ボタンを押すと、TurtleSimの亀が左へ回転する。
  3. [右回転]ボタンを押すと、TurtleSimの亀が右へ回転する。
  4. 直線速度を数値入力指定し送信ボタンを押すと、入力した速度分だけTurtleSimの亀が前進する。
  5. 右回転速度を数値入力指定し送信ボタンを押すと、入力した速度分だけTurtleSimの亀が右回転する。
  6. 左回転速度を数値入力指定し送信ボタンを押すと、入力した速度分だけTurtleSimの亀が左回転する。
  7. TurtleSimの亀の位置(x,y,θ)を取得し、表示する
  8. TurtleSimの亀の速度(x_vel,y_vel)を取得し、表示する

Screenshot from 2019-11-18 02-35-24.png

(ROSの機能の一つである「rqt」でいいじゃんって話なんですが、
今回はデモってことで、適当に応用していただけたらって感じで...)

「bootstrap」はWebGUIを作るためのCSSフレームワークです。
「bootstrap」以外にも「Semantic UI」「Foundation」などが有名です。

今回は世界中で最も使われている「bootstrap」を用いました。

作業時間は「環境構築」~「実装」までで、2時間くらいです。

環境

  • OS:ubuntu 18.04
  • ROSバージョン:ROS1 Melodic Morenia
  • 使用ブラウザ:Google Chrome

記事の対象者

  • ROSを触ったことがある
  • WebGUIを作成したことがない

ROS環境の構築

パッケージの作成

$ cd ~/catkin_ws/src
$ catkin_create_pkg web_gui

bootstrapとVueの環境の構築

bootstrapの環境の構築

以下のページのダウンロードボタンを押して、bootstrapをダウンロードしてください。
https://getbootstrap.com/docs/4.3/getting-started/download/
Screenshot from 2019-10-12 18-06-03.png

解凍するとcssファイルとjsファイルが入っています。
今回はjsファイルを用いるので、ダウンロードしたjsファイルをパッケージファイル(web_gui)の
「contents」の中に入れてください。

Vueの環境の構築

Vueのインストール方法や概要は以下に載っています。
https://jp.vuejs.org/v2/guide/installation.html

今回はCDNで直接埋め込むため、インストールする必要はないです。

UIテンプレート

フリーのものから有料のものまでいろいろありますが、
中でもTimさんが作成された「Vue Black Dashboard」が僕の好みなので、
これを利用させていただきました。
https://www.creative-tim.com/product/vue-black-dashboard?affiliate_id=116187

MITライセンスです。

このページから[FREE DOWNLOAD]を押して、ツールをダウンロードしてパッケージファイル(web_gui)の「contents」の中に入れてください。

アイコン

流行りのGUIを真似てみます。

単色カラーのフラットアイコンが流行ってますので、以下の「nucleo-test-pack」を使用させていただきました。

icon.png

こちらもフリーのものを使用させていただきました。

アイコンについては以下からダウンロードして、パッケージファイル(web_gui)の「contents」の中に入れてください。
https://nucleoapp.com/free-icons

その他の設定

html環境設定(読み飛ばしていただいて構いません)

僕がエディタにVisual Studio codeを使っており、htmlを使うのが初めてだったため、
開発を加速させるためにVisual Studio codeの設定を行いました。

インデントの自動調整機能です。
以下を設定して、コマンド[Ctrl]+[Shift]+[i]
https://qiita.com/maron8676/items/017cd830ab0c5fb8bcac

GUI作成の流れ

  1. HtmlでGUIの画面を作成
  2. GUIの機能を「.js」に書く

HtmlでGUIの画面を作成

テンプレートが配布されていなかったので以下のページで「名前をつけて保存」を行い、ライセンス規約に従って著作者を書きました。
https://demos.creative-tim.com/vue-black-dashboard/#/dashboard

ライセンス:https://demos.creative-tim.com/vue-black-dashboard/documentation/licence.html

ファイル名を「turtle_sim_controller.html」として保存し、ダウンロードしたファイルをパッケージファイル(web_gui)の「contents」の中に入れてください。

アイコンの置き換え

logo.png

メニューバーのアイコンを置き換えます。
先ほどダウンロードしたアイコンから好きなものを選んで、貼り付けてください。

「turtle_sim_controller.html」の以下のくくりがメニューバーのコードになっているので、このあたりを書き換えましょう。

僕は以下のアイコンに置き換えました。

turtle_sim_controller.html
          <div id="style-3" class="sidebar-wrapper">
            <!-- 左サイドバーの設定 -->
            <div class="logo"><a href="http://www.creative-tim.com/" aria-label="sidebar mini logo"
                class="simple-text logo-mini">
                <!-- 亀アイコン追加 -->
                <div class="photo"><img src="./nucleo-test-pack/svg/outline/24px/turtle.svg"></div>
              </a><a href="http://www.creative-tim.com/" class="simple-text logo-normal">
                MENU
              </a></div>
            <ul class="nav">
              <li class="nav-item"><a href="nucleo-test-pack/iconfont/nc-demo_outline/demo/css/style.css"
                  class="nav-link"><i class="nc-icon nc-barcode-qr"></i>
                  <!-- コンパスアイコン追加 -->
                  <div class="photo"><img src="./nucleo-test-pack/svg/outline/24px/compass-05.svg"></div>
                  <p>Mapping</p>
                </a></li>
              <li class="nav-item"><a href="nucleo-test-pack/iconfont/nc-demo_outline/demo/css/style.css"
                  class="nav-link"><i class="nc-icon nc-archive-paper"></i>
                  <!-- スライダアイコン追加 -->
                  <div class="photo"><img src="./nucleo-test-pack/svg/outline/24px/preferences.svg"></div>
                  <p>Setting parameters</p>
                </a></li>
              <li class="nav-item"><a href="nucleo-test-pack/iconfont/nc-demo_outline/demo/css/style.css"
                  class="nav-link"><i class="nc-icon nc-barcode-qr"></i>
                  <!-- 紙飛行機アイコン追加 -->
                  <div class="photo"><img src="./nucleo-test-pack/svg/outline/24px/send-2.svg"></div>
                  <p>Controller</p>
                </a></li>
              <li class="nav-item"><a href="nucleo-test-pack/iconfont/nc-demo_outline/demo/css/style.css"
                  class="nav-link"><i class="nc-icon nc-archive-paper"></i>
                  <!-- ノートアイコン追加 -->
                  <div class="photo"><img src="./nucleo-test-pack/svg/outline/24px/notes.svg"></div>
                  <p>Log</p>
                </a></li>
              <li class="nav-item"><a href="nucleo-test-pack/iconfont/nc-demo_outline/demo/css/style.css"
                  class="nav-link"><i class="nc-icon nc-barcode-qr"></i>
                  <!-- 鎖アイコン追加 -->
                  <div class="photo"><img src="./nucleo-test-pack/svg/outline/24px/link-72.svg"></div>
                  <p>Setting software</p>
                </a></li>
            </ul>

Publish

pub.png

続いてPublisher部分を書きます。

turtle_sim_controller.html
              <!-- Publisherのくくり -->
              <div class="col-md-6">
                <div class="card">
                  <!---->
                  <div class="card-header">
                    <h5 class="title">Publisher</h5>
                  </div>
                  <div class="card-body">

                    <h5>ボタン入力</h5>
                    <!-- 速度Pub -->
                    <div class="row">
                      <div class="col-md-4"><button type="button" class="btn btn-block btn btn-info"
                          id=button_rotate_left>
                          <!---->左回転</button></div>
                      <div class="col-md-4"><button type="button" class="btn btn-block btn btn-info" id=button_straight>
                          <!---->直進</button></div>
                      <div class="col-md-4"><button type="button" class="btn btn-block btn btn-info"
                          id=button_rotate_right>
                          <!---->右回転</button></div>
                    </div>
                    <br></br>

                    <h5>数値入力</h5>
                    <div class="row">
                      <div class="col-md-4">
                        <div class="form-group">
                          <label class="control-label" for="text1">
                            左回転
                          </label>
                          <input aria-describedby="addon-right addon-left" placeholder="0" class="form-control"
                            type="text" id="textbox_rotate_left">
                        </div>
                      </div>
                      <div class="col-md-4">
                        <div class="form-group">
                          <label class="control-label">
                            直進
                          </label>
                          <!----><input aria-describedby="addon-right addon-left" placeholder="0" class="form-control"
                            type="text" id="textbox_straight">
                          <!---->
                        </div>
                      </div>
                      <div class="col-md-4">
                        <div class="form-group">
                          <label class="control-label">
                            右回転
                          </label>
                          <!----><input aria-describedby="addon-right addon-left" placeholder="0" class="form-control"
                            type="text" id="textbox_rotate_right">
                          <!---->
                        </div>
                      </div>
                    </div>
                    <div class="card-footer">
                      <button type="button" class="btn btn-primary" fill="" id=cmd_send>
                        送信</button></div>
                  </div>
                </div>
              </div>

ここでは、「グリッドシステム」と「id」について説明します。

  • グリッドシステム

Publisher部分の横枠の大きさは以下のコードで指定します。

class="col-md-6"

これは「横方向に12分割されているグリッドの6(半分)をPublisher部分に使いますよ」という意味です。
残りの6はSubscriber部分に使います。
bootstrapでは枠の大きさの指定に「width、height」などの固定長を使わないようにすることで、
GUIを様々な画面の大きさのディスプレイに対応させています。
ボタンの横幅の大きさも同様に「class="col-md-4"」を使っています。
これは「横方向に12分割されているグリッドの4(全体の1/3)を左回転ボタン、直進ボタン、右回転ボタンに使いますよ」という意味です。

  • id

jsファイルとの値の受け渡しにつかう重要な機能です。
ROSとGUIで値を受け渡すには、htmlで指定したidと後に書くjsファイルのidを一致させておく必要があります。

Subscribe

sub.png

続いてSubscriber部分を書きました。

主機能としてはPublisherと変わらないため、特に解説しません。

turtle_sim_controller.html
       <!-- Subscriberのくくり -->
              <div class="col-md-6">
                <div class="card">
                  <div class="card-header">
                    <h5 class="title">Subscriber</h5>
                  </div>
                  <div class="card-body">

                    <!-- Sub位置 -->
                    <div class="row">
                      <div class="col-md-4">
                        <div class="form-group">
                          <label class="control-label">
                            位置X
                          </label>
                          <div class="form-control">
                            <value id="pose_x">0</value>
                          </div>
                        </div>
                      </div>
                      <div class="col-md-4">
                        <div class="form-group"><label class="control-label">
                            位置Y
                          </label>
                          <div class="form-control">
                            <value id="pose_y">0</value>
                          </div>
                        </div>
                      </div>
                      <div class="col-md-4">
                        <div class="form-group"><label class="control-label">
                            角度
                          </label>
                          <div class="form-control">
                            <value id="pose_theta">0</value>
                          </div>
                        </div>
                      </div>

                      <!-- Sub速度 -->
                      <div class="col-md-6">
                        <div class="form-group"><label class="control-label">
                            直進速度
                          </label>

                          <div class="form-control">
                            <value id="pose_linear_velocity">0</value>
                          </div>
                        </div>
                      </div>
                      <div class="col-md-6">
                        <div class="form-group"><label class="control-label">
                            回転速度
                          </label>
                          <div class="form-control">
                            <value id="pose_angular_velocity">0</value>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>

これで見た目の部分は完成です。

次にROSとhtmlファイル(表示系)をつなぐためにjsファイル(機能系)を書きます。

先に、turtle_sim_controller.htmlの

内にjsファイル(turtle_sim.js)を定義しておきましょう。
turtle_sim_controller.html
<script src="turtle_sim.js"></script>

jsファイル(機能系)を書く。

  • 使用するトピックの定義
  • Callback関数の定義
    • 値の受け渡して表示する
  • ボタンに機能をつける
turtle_sim.js
var ros = new ROSLIB.Ros({ url : 'ws://' + location.hostname + ':9000' });

ros.on('connection', function() {console.log('websocket: connected'); });
ros.on('error', function(error) {console.log('websocket error: ', error); });
ros.on('close', function() {console.log('websocket: closed');});

// Topicの定義
var ls = new ROSLIB.Topic({
  ros : ros,
  name : '/turtle1/pose',
  messageType : 'turtlesim/Pose'
});

var vel = new ROSLIB.Topic({
  ros : ros,
  name : '/turtle1/cmd_vel',
  messageType : 'geometry_msgs/Twist'
});

ls.subscribe(function(message) {
    // idで検索して、値を入れる
     str = JSON.stringify(message);

        // 位置情報
        document.getElementById("pose_x").innerHTML = Math.round(message["x"] * 100) / 100;
        document.getElementById("pose_y").innerHTML = Math.round(message["y"] * 100) / 100;
        document.getElementById("pose_theta").innerHTML = Math.round(message["theta"] * 100) / 100;
        document.getElementById("pose_linear_velocity").innerHTML = message["linear_velocity"];
        document.getElementById("pose_angular_velocity").innerHTML = message["angular_velocity"];

      });

// メッセージの定義
var twist = new ROSLIB.Message({
     linear : {
       x : 0,
       y : 0,
       z : 0
     },
     angular : {
       x :  0,
       y :  0,
       z :  0
     }
   });

var pose = new ROSLIB.Message({
  x : 0.00,
  y : 0.00,
  theta : 0.00,
  linear_velocity: 0.0,
  angular_velocity: 0.0
});

// 左回転ボタンを押したとき
$('#button_rotate_left').on('click', function(e){
  v = new ROSLIB.Message({linear:{x:0,y:0,z:0}, angular:{x:0,y:0,z:1}});
  vel.publish(v);
});

// 直進ボタンを押したとき
$('#button_straight').on('click', function(e){
  v = new ROSLIB.Message({linear:{x:1,y:0,z:0}, angular:{x:0,y:0,z:0}});
  vel.publish(v);
});

// 左回転ボタンを押したとき
$('#button_rotate_right').on('click', function(e){
  v = new ROSLIB.Message({linear:{x:0,y:0,z:0}, angular:{x:0,y:0,z:-1}});
  vel.publish(v);
});

// 左回転ボタンを押したとき
$('#cmd_send').on('click', function(e){
  // テキストボックスの読み込み
  text_rotate_left = Number(document.getElementById('textbox_rotate_left').value);
  text_straight = Number(document.getElementById('textbox_straight').value);
  text_rotate_right = Number(document.getElementById('textbox_rotate_right').value);

  // 回転方向の入力を算出
  text_rotate = text_rotate_left - text_rotate_right;

  v = new ROSLIB.Message({linear:{x:text_straight,y:0,z:0}, angular:{x:0,y:0,z:text_rotate}});
  vel.publish(v);
});

サーバノードの作成

『[Raspberry Piで学ぶ ROSロボット入門]
(https://www.amazon.co.jp/Raspberry-Pi%E3%81%A7%E5%AD%A6%E3%81%B6-ROS%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E5%85%A5%E9%96%80-%E4%B8%8A%E7%94%B0-%E9%9A%86%E4%B8%80/dp/4822239292/ref=sr_1_fkmr0_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&keywords=Rasberrypi%E3%81%A7%E5%AD%A6%E3%81%B6+ROS&qid=1570953377&s=books&sr=1-1-fkmr0)』によると、これでサーバノードを立てることができるらしいです。僕も中身はあまり理解してません。

webserver.py
#!/usr/bin/env python
# coding:utf-8

# Copyright (c) 2017 Ryuichi Ueda

import rospy
import os
import SimpleHTTPServer

def kill():
    os.system("kill -KILL " + str(os.getpid()))

os.chdir(os.path.dirname(__file__) + "/../contents")
rospy.init_node("webserver")
rospy.on_shutdown(kill)
SimpleHTTPServer.test()

実行

  1. roscoreの起動
    $ roscore

  2. turtlesimの実行
    $ rosrun turtlesim turtlesim_node

  3. Webサーバノードの立ち上げ
    $ python webserver.py

  4. Webサーバノードにアクセス
     ブラウザで以下にアクセスする。
     http://localhost:8000/

Peek 2019-11-18 03-38.gif

終わりに

いい感じに動きましたかね(?)

今回は世の中にあるいいものを使って、WEBGUIを作成しました。
1からUIのデザインをしてみたり、アイコンを作成してみたりしてみても面白いかもしれません。

参考

ROSではじめるロボットプログラミング

[Raspberry Piで学ぶ ROSロボット入門]
(https://www.amazon.co.jp/Raspberry-Pi%E3%81%A7%E5%AD%A6%E3%81%B6-ROS%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E5%85%A5%E9%96%80-%E4%B8%8A%E7%94%B0-%E9%9A%86%E4%B8%80/dp/4822239292/ref=sr_1_fkmr0_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&keywords=Rasberrypi%E3%81%A7%E5%AD%A6%E3%81%B6+ROS&qid=1570953377&s=books&sr=1-1-fkmr0)

トラブルシュート

jsのプログラムが更新されない

→キャッシュを削除する
https://teratail.com/questions/77208

2
3
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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?