概要
「bootstrapの使い方」〜「ROS環境への実装」が主な内容になります。
したがって、本記事は以下のものを作成して実装したらゴールとします。
GUI機能:
- [前進]ボタンを押すと、TurtleSimの亀が前進する。
- [左回転]ボタンを押すと、TurtleSimの亀が左へ回転する。
- [右回転]ボタンを押すと、TurtleSimの亀が右へ回転する。
- 直線速度を数値入力指定し送信ボタンを押すと、入力した速度分だけTurtleSimの亀が前進する。
- 右回転速度を数値入力指定し送信ボタンを押すと、入力した速度分だけTurtleSimの亀が右回転する。
- 左回転速度を数値入力指定し送信ボタンを押すと、入力した速度分だけTurtleSimの亀が左回転する。
- TurtleSimの亀の位置(x,y,θ)を取得し、表示する
- TurtleSimの亀の速度(x_vel,y_vel)を取得し、表示する
(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
-
参考書
- ROSに触れたことがない方でも、以下の8.2節(P68)まで読んでいただけると、以下の理解が容易になると思います。
- 『ROSではじめるロボットプログラミング/小倉 崇』
パッケージの作成
$ cd ~/catkin_ws/src
$ catkin_create_pkg web_gui
bootstrapとVueの環境の構築
bootstrapの環境の構築
以下のページのダウンロードボタンを押して、bootstrapをダウンロードしてください。
https://getbootstrap.com/docs/4.3/getting-started/download/
解凍すると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」を使用させていただきました。
こちらもフリーのものを使用させていただきました。
アイコンについては以下からダウンロードして、パッケージファイル(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作成の流れ
- HtmlでGUIの画面を作成
- 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」の中に入れてください。
アイコンの置き換え
メニューバーのアイコンを置き換えます。
先ほどダウンロードしたアイコンから好きなものを選んで、貼り付けてください。
「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
続いてPublisher部分を書きます。
<!-- 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
続いてSubscriber部分を書きました。
主機能としてはPublisherと変わらないため、特に解説しません。
<!-- 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)を定義しておきましょう。<script src="turtle_sim.js"></script>
jsファイル(機能系)を書く。
- 使用するトピックの定義
- Callback関数の定義
- 値の受け渡して表示する
- ボタンに機能をつける
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);
});
サーバノードの作成
#!/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
実行
-
roscoreの起動
$ roscore -
turtlesimの実行
$ rosrun turtlesim turtlesim_node -
Webサーバノードの立ち上げ
$ python webserver.py -
Webサーバノードにアクセス
ブラウザで以下にアクセスする。
http://localhost:8000/
終わりに
いい感じに動きましたかね(?)
今回は世の中にあるいいものを使って、WEBGUIを作成しました。
1からUIのデザインをしてみたり、アイコンを作成してみたりしてみても面白いかもしれません。
参考
トラブルシュート
jsのプログラムが更新されない
→キャッシュを削除する
https://teratail.com/questions/77208