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

# 概要

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

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

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

![Screenshot from 2019-11-18 02-35-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266068/b13d318e-ad5b-9c4d-d999-5341392c570e.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環境の構築



- ROS_Wiki
    - http://wiki.ros.org/ja/ROS/Tutorials/InstallingandConfiguringROSEnvironment

- 参考書
    - ROSに触れたことがない方でも、以下の8.2節(P68)まで読んでいただけると、以下の理解が容易になると思います。
    - [『ROSではじめるロボットプログラミング/小倉 崇』](https://www.amazon.co.jp/ROS%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%8B%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E2%80%95%E3%83%95%E3%83%AA%E3%83%BC%E3%81%AE%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E7%94%A8%E3%80%8C%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF%E3%80%8D-I%E3%83%BB-BOOKS-%E5%B0%8F%E5%80%89-%E5%B4%87/dp/4777519015/ref=sr_1_1?s=books&ie=UTF8&qid=1547359326&sr=1-1&keywords=ROS%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%8B)

### パッケージの作成

$ 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266068/ea7bdb3a-69c6-bc35-35a9-3ccd7d6e1aaf.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266068/3c4d38cd-9be7-c0c8-4546-973112c632a4.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の画面を作成
3. 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266068/6944ca35-90d6-1dd8-90ac-c28fbfb80a28.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266068/b9d651ee-bc1f-bc98-82ac-7e770bdebd77.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266068/24f4d778-ebf3-a600-617c-cbb156fdeed9.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の<head>内に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()
```

- シンボリックリンクの作成
$ ln -s turtle_sim_controller.html index.html
$ ln -s turtle_sim.js index.js


- キャッシュの除去(jsのシンボリックリンクの更新後)
https://helpx.adobe.com/jp/legacy/kb/222659.html


## 実行
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266068/cdd1e5bf-2146-951d-a685-79b8bb3a7fc0.gif)


# 終わりに

いい感じに動きましたかね（？）

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

# 参考
[ROSではじめるロボットプログラミング](https://www.amazon.co.jp/ROS%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%8B%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E2%80%95%E3%83%95%E3%83%AA%E3%83%BC%E3%81%AE%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88%E7%94%A8%E3%80%8C%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF%E3%80%8D-I%E3%83%BB-BOOKS-%E5%B0%8F%E5%80%89-%E5%B4%87/dp/4777519015/ref=sr_1_1?s=books&ie=UTF8&qid=1547359326&sr=1-1&keywords=ROS%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%8B)

[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