ROS講座63 webでtwistを送る

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

| 項目 | 値 |
|:-:|:-:|
| CPU | Core i5-8250U |
| Ubuntu | 16.04 |
| ROS | Kinetic |

インストールについては[ROS講座02 インストール](https://qiita.com/srs/items/e0e0a9dc3f94c2d3348e)を参照してください。
またこの記事のプログラムはgithubにアップロードされています。[ROS講座11 gitリポジトリ](https://qiita.com/srs/items/25a56a6abda41dcf396e)を参照してください。

# 概要
今までロボットを動かす場合は、geometry_msgs/Twist型が必要でした。しかし、JoyConを使ってtTwistを送る方法しかありませんでした。これではいつでもJoyConを持ち歩かないといけなく不便なので、GUIからTwistを送りたいです。
今回はHTMLのCanvas要素を使ってTwistを送ってみます。

# ソースコード
```html:web_lecture/www/twist.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="content-type" content="text/html;charset=utf-8"/>
    <style type="text/css">
    <!--
      *{ padding:0px; margin:0px;}
    -->
    </style>
    <script src="https://static.robotwebtools.org/EventEmitter2/current/eventemitter2.min.js"></script>
    <script src="https://static.robotwebtools.org/roslibjs/current/roslib.min.js"></script>
  </head>
  <title>HTML5</title>
  <body>
    <p>status： <label id="state">Disconnect</label></p>
    <h1>/cmd_vel</h1>
    <canvas id='canvas' style="background-color:#EEEEEE;"></canvas>
    <p id="linearX" >linearX:  0.0 m/s</p>
    <p id="angularZ">angularZ: 0.0 rad/s</p>
    <script>

if(!Twist){
    var Twist = {
        ros : null,
        name : "",
        init : function(){
            this.ros = new ROSLIB.Ros();
            this.ros.on('error', function(error) {
                document.getElementById('state').innerHTML = "Error";
            });
            this.ros.on('connection', function(error) {
                document.getElementById('state').innerHTML = "Connect";
            });
            this.ros.on('close', function(error) {
                document.getElementById('state').innerHTML = "Close";
            });
            this.ros.connect('ws://' + location.hostname + ':9090');
        },
        send : function(linearX, angularZ){
            var pub = new ROSLIB.Topic({
                ros : this.ros,
                name : '/cmd_vel',
                messageType : 'geometry_msgs/Twist'
            });
            var twist = new ROSLIB.Message({
              linear:{x:linearX, y:0.0, z:0.0}, 
              angular:{x:0.0, y:0.0, z:angularZ}
            });
            pub.publish(twist);
        }
    }
    Twist.init();

    window.onload = function(){
    };
    window.onunload = function(){
        Twist.ros.close();
    };
}

//position -1.0~1.0
last_X=0.0;
last_Y=0.0;
last_enable=false;

window.onload=function(){
  canvas=$('canvas');
  expandCanvas();
  reticle();
}

function expandCanvas(){
  var b = document.body;
  var d = document.documentElement;
  canvas.width  = 400;
  canvas.height = 400;
}

canvas.onmousedown = function(e){
  last_X=(e.pageX - canvas.offsetLeft - 200) / 200;
  last_Y=(e.pageY - canvas.offsetTop  - 200) / 200;
  last_enable=true;
}

canvas.onmousemove = function(e){
  last_X=(e.pageX - canvas.offsetLeft - 200) / 200;
  last_Y=(e.pageY - canvas.offsetTop  - 200) / 200;
}

canvas.onmouseup=function(e){
  last_X=0.0
  last_Y=0.0
  last_enable=false;
  Twist.send(0.0, 0.0);
}

function reticle(){
  var target=$("canvas");
  var context=target.getContext('2d');
  center_x=200;
  center_y=200;
  line_a=10;
  line_b=100;

  context.beginPath();
  context.moveTo(center_x+line_a, center_y);
  context.lineTo(center_x+line_b, center_y);
  context.closePath();
  context.stroke();

  context.beginPath();
  context.moveTo(center_x-line_a, center_y);
  context.lineTo(center_x-line_b, center_y);
  context.closePath();
  context.stroke();

  context.beginPath();
  context.moveTo(center_x, center_y+line_a);
  context.lineTo(center_x, center_y+line_b);
  context.closePath();
  context.stroke();

  context.beginPath();
  context.moveTo(center_x, center_y-line_a);
  context.lineTo(center_x, center_y-line_b);
  context.closePath();
  context.stroke();
}

function interval_process(){
  if(last_enable){
    var linearX  = -0.5*last_Y;
    var angularZ = -1.0*last_X;

    document.getElementById("linearX" ).textContent = "linearX: " +linearX +" m/s" ;
    document.getElementById("angularZ").textContent = "angularZ: "+angularZ+" rad/s" ;
    console.log(linearX, angularZ);
    Twist.send(linearX, angularZ);
  }
}
setInterval("interval_process()",100);
		
function $(id){
  return document.getElementById(id);
}
    </script>
  </body>
</html>
```

`if(!Twist){}`の中はTwistを送るためにwebsocketの通信を行っているところです。前のpub.htmlとほぼ同じ内容です。jsonで複数階層のrosmsgを表すときは`{a:{a0:0, a1:1}, b:{b0:2, b1:3}}`のようにします。
`window.onload=function(){}`は起動時に呼ばれるcallbackを登録しています。

その下では`canvas.onmousedown = function(e){}`のようにcanvasでいイベントが起きた時に実行するcallbackを登録しています。
今回登録したのは以下のようなイベントです。

| イベント | xy座標 | enable |
|:-:|:-:|:-:|
| canvas.onmousedown | 現在位置にセット | trueにセット |
| canvas.onmousemove | 現在位置にセット | 変化なし |
| canvas.onmouseup | 0,0にセット | falseにセット |

その下の`function interval_process(){}`と`setInterval("interval_process()",100);`では10Hzで実行するcallbackを設定しています。ここではenable==trueの時に保存されているxyに合わせてtwistを送信する`Twist.pub()`を呼んでいます。

# 実行
以下のように2つのターミナルを開いて、そのうえでブラウザで`localhost:8085/web_lecture/twist.html`にアクセスします。

```shell:1つ目のターミナルで
roslaunch vis_lecture move_robot.launch 
```

```shell:2つ目のターミナルで
roslaunch web_lecture bridge.launch 
```

![web_twist.gif](https://qiita-image-store.s3.amazonaws.com/0/254442/cc1121a2-ea41-14d7-1278-5b578c88c115.gif)

# 参考
[canvasオブジェクトを作る](http://forse.hatenablog.com/entry/2014/07/15/195447)
[javascriptで一定間隔にプログラムを実行](https://www.pazru.net/js/timer/1.html)
[多階層のjsonの書き方](https://qiita.com/harukingo/items/db1cdee35c93c0ab6a3f)
[javascriptのイベント](https://qiita.com/nekoneko-wanwan/items/9af7fb34d0fb7f9fc870)

# 目次ページへのリンク
[ROS講座の目次へのリンク](https://qiita.com/srs/items/5f44440afea0eb616b4a)