ROS講座53 ブラウザでpub subする

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

| 項目 | 値 |
|:-:|:-:|
| CPU | Core i5-8250U |
| Ubuntu | 20.04 |
| ROS | Noetic |

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

# 概要
rosbridgeはROSの通信とwebsocketを相互変換するROSパッケージです。ブラウザはjavascriptでwebsocketの通信を扱えるので、これによってwebページ経由でROSと通信できます。
今回はTalkerとListenerのサンプルを解説します。


# インストール
rosbridgeのインストールは以下のコマンドで行えます。

```shell
sudo apt install ros-noetic-rosbridge-suite
```

# ソースコード

## launch
前回使ったroswwwに加えて、rosbridgeのlaunchを起動させるだけです。

```xml:web_lecture/launch/bridge.launch
<launch>
  <include file="$(find roswww)/launch/roswww.launch" />
  <include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch" />
</launch>
```

## talker(html)
publishを行うwebページ（javascript）の例です。

```html:web_lecture/www/pub.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>
    <title>ROS WEB Publisher</title>
    <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>

  <body>
    <script type="text/javascript">

        <!--
if(!Talker){
    var Talker = {
        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(){
            if(document.getElementById("comment").value.length == 0) return;
            var comment = document.getElementById("comment").value;
            var pub = new ROSLIB.Topic({
                ros : this.ros,
                name : '/chatter',
                messageType : 'std_msgs/String'
            });

            var str = new ROSLIB.Message({data : comment});
            pub.publish(str);
        }
    }
    Talker.init();

    window.onload = function(){
    };
    window.onunload = function(){
        Talker.ros.close();
    };
}
        //-->
    </script>
    <p>status： <label id="state">Disconnect</label></p>

    <form action="javascript:Talker.send();" id="ui">
      <label>Comment: </label>
      <input type="text" id="comment" size="20" />
      <input type="submit" value="send" id="btn" /><br/>
    </form>
  </body>
</html>
```

詳しく解説していきます。
以下のような部分でTalkerの宣言を行っています。

```javascript:Talkerの宣言
var Talker = {
...
}
```

以下の部分で初期化動作の宣言をしています。function以下の最初の行ではROSLIBを設定しています。その下の9行はrosbridgeのコネクションの状態が変わった時の処理です。下から2行目の`this.ros.connect('ws://' + location.hostname + ':9090');`ではwebsocket通信を開始します。rosbridgeは標準では9090ポートで通信を行います。

```javascript:Talkerの初期化の宣言
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');
}
```

以下がpublisherのコードです。今回のスクリプトでは「send」ボタンを押すと毎回publisherを生成してpublishします。
1行目は送る文字列（id:commentのテキストボックスから持ってくる）が0文字なら飛ばす処理です。
4~8行目がpublisherの生成です。トピック名とメッセージTypeを記述します。
`var str = new ROSLIB.Message({data : comment});`でメッセージを作っています。括弧の中でフィールド名を持つ構造体として生成します。

```javascript:publishのコード
send : function(){
  if(document.getElementById("comment").value.length == 0) return;
  var comment = document.getElementById("comment").value;
  var pub = new ROSLIB.Topic({
    ros : this.ros,
    name : '/chatter',
    messageType : 'std_msgs/String'
  });

  var str = new ROSLIB.Message({data : comment});
  pub.publish(str);
}
```

## listener(html)

```html:web_lecture/www/sub.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>
        <title>ROS WEB Subscriber</title>
        <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>
    <body>
    <script type="text/javascript">
    <!--
if(!Listener){
    var Listener = {
        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');

            var sub = new ROSLIB.Topic({
                ros : this.ros,
                name : '/chatter',
                messageType : 'std_msgs/String'
            });
            sub.subscribe(function(message) {
                var res = message.data;
                var el = document.createElement("p");
                el.innerHTML = res
                document.getElementById("talk").appendChild(el);
            });
        }
    }
    Listener.init();

    window.onload = function(){
    };
    window.onunload = function(){
        Listener.ros.close();
    };
}
        //-->
    </script>
    <p>status： <label id="state">Disconnect</label></p>
    <div id="talk"><p></p></div>
  </body>
</html>
```

以下の部分でSubscriberを設定します。publisherと同じようにTopic名とメッセージTypeを宣言します。その下で、callback関数の設定をします。今回のスクリプトではやってきた文字列を`<p>`要素（id:talk）に追記していきます。

```javascript:Subscriberの設定
var sub = new ROSLIB.Topic({
  ros : this.ros,
  name : '/chatter',
  messageType : 'std_msgs/String'
});
sub.subscribe(function(message) {
  var res = message.data;
  var el = document.createElement("p");
  el.innerHTML = res
  document.getElementById("talk").appendChild(el);
});
```

# 実行
launchを実行します。

```xml
roslaunch web_lecture bridge.launch
```

この後にブラウザを2つ開いて`localhost:8085/web_lecture/pub.html`と`localhost:8085/web_lecture/sub.html`のページにアクセスします。以下のようにpub.htmlのページでテキストボックスに文字を入れて「send」を押すと、sub.htmのページに表示されます。

![web_pubsub.gif](https://qiita-image-store.s3.amazonaws.com/0/254442/a663d657-aca5-453c-4a25-148fe77b9773.gif)

# 参考
[ROSのWebインタフェースを作る](https://qiita.com/yoneken/items/7e45ad3fcf8010ed98ed)

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