ブラウザからROS Topic、MQTT Topic を送受信しROS nodeと通信する その１

タブレット（iPad上のsafari）上でROS上のシステムと通信し、情報表示するシステムを作ってみたので備忘録として記事にしてみます。

今回の構成は自分でもすぐ再現できるように[docker環境](https://github.com/shun/docker-ros-mqtt-topic-example)で作成しています。
以下の内容として、OS はubuntu 16.04（kinetic）をベースに話を進めます。
多分、18.04（melodic）でも変わらないと思っています。
構成的な関係上、slaveはsupervisor をインストールし各パッケージを起動するようにします。

# 構成

![システムダイアログ](http://www.plantuml.com/plantuml/svg/VLF1RXCn4BtlLunouMHK9vVqri89gLK32I244B9tXxBKMGVRcprK-NViOLpPoQgLAiNpdltUl9czyK6vCEvDux0DQdWq28ixsoe3jmxL0fwR1g30xfKFw63pFa4G_xuxwrlhy0Se3_oRW0z39FvIRRoqiIv8kP9omQIVXro9rDEPnDQe8vOANr735jqHdIILoH-TdDDONv606J14XNIUOrrVNV_dJQYzRP-OdCh9IUNbqyCM1jiXU-5-jUkxHnIUF77ZNCoi-u-x7Hn6RNh_cpdxlo4gZRgY-57xrlMwv7Zcd6R2sIbjC4ZsKoBld9qyEbbRDRt-u-dfV2YVRk9_Q-pOmNCjysMpfOYE1qzGjXj5daghcixdOrg-Z6b940qCH6UdGRmsdMA6_UiikLwl4ez3fSfgbPgYdYu2Mr8gCt4nmFszvQpIgvJKARts1kvklprytZML68Zn8AeKyl10YDeNNvn3AAHKBXi-qn7PJy59rTn2mvzTgjKVLrvBWcRMlMBJzNpX4iezp26yj9SAz6PUl6BoUHdoqZOtE7JZtlm3)
<details><summary>plantuml</summary><div>

```uml
@startuml
rectangle "Local Area" {

  rectangle "Master PC" {
    [roscore] as roscore
    Interface "Port11311" as ros_port11311
  }

  rectangle "Slave PC" {
    [Web Server1] as wsvr1
    Interface "Port80" as lnet_port80
    Interface "Port9090" as websocket_port9090

    rectangle "ROS node" {
      [rosbridge-server] as rosbridge
      [MQTT publisher] as mqtt_pub
      [MQTT subscriber] as mqtt_sub
    }
  }

  rectangle "Tablet1" {
    rectangle "Browser1" {
      [roslibjs] as roslibjs
    }
  }
}

cloud {
  rectangle "VPS" as vps {
    [MQTT Broker] as mqtt_broker
    [Web Server2] as wsvr2
    wsvr1 -down- lnet_port80
    Interface "Port80" as inet_port80
    Interface "Port1883" as mqtt_port1833
  }
}

rectangle "Tablet2" {
  rectangle "Browser2" {
    [MQTT.js] as mqttjs
  }
}

' layout
mqtt_port1833 -up- mqtt_broker
mqtt_sub -- mqtt_port1833
mqtt_pub -- mqtt_port1833
wsvr2 -- inet_port80
mqtt_port1833 -- mqttjs
inet_port80 -- Browser2
roscore -- ros_port11311
rosbridge -- websocket_port9090
ros_port11311 -- rosbridge
ros_port11311 -- mqtt_pub
ros_port11311 -- mqtt_sub
websocket_port9090 -- roslibjs
lnet_port80 -down- Browser1
@endum
```

</div></details>

# roscoreとブラウザの通信

## 環境構築
roscore とブラウザが通信する仕組みとして標準的なものが何なのかよくわかっていませんが、rosbridge-serverというパッケージがあり、roscore（rosbridge）とwebsocketで通信できるようになります。


```bash:install

$ apt install -y ros-kinetic-rosbridge-server
```

websocketで通信できる準備ができたら、ブラウザ側が通信するように [roslib.js](https://github.com/RobotWebTools/roslibjs) というjavascriptのライブラリを使用して通信します。

```bash:install

$ npm install roslib
```

環境的なセットアップはこの辺で十分かと思います。
フロントエンドとしては、vue.jsを使用しますがvue.jsに関しては大したことをやっていないので細かいことは割愛していきたいと思います。
今回のROS Topic/MQTT Topic を受信する部分のみにフォーカスしていきたいと思います。

## ブラウザ側の実装

### vue でプロジェクトのセットアップ

適当にプロジェクトを作成します。
プロジェクトの作成には vue ui で作成していきます。
コンテナの外部からアクセスできるようにオプションで「-H 0.0.0.0」を追加して起動します。

```bash:

$ vue ui -H 0.0.0.0
```

プロジェクトの作成。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65929/9bebb59a-08b1-7477-dfdd-36ce11a0bcaf.png)

プリセットは「手動」で。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65929/85838911-b2ca-2154-5308-ace1245e64f4.png)

ONに変更したのはTypeScriptのみ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65929/b6b73793-191c-84c0-7eb0-d4f1836c071f.png)

linterの部分はよくわかっていないけど、とりあえず「ESLint with prevention only」を設定。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65929/2a8b341d-c3f3-9c0e-e170-e81e27f54b6d.png)


プロジェクト作成後、roslib.jsをインストールするため、左のメニュー（？）の「依存」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65929/7849332b-a7ce-2958-4d96-27f19fbd2aaf.png)

「依存をインストール」をクリックして「roslib」を入力しインストール。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65929/83d8265c-1182-d51e-bfb6-ab841b3963c2.png)

左のメニュー（？）の「タスク」をクリック後、「タスクを実行」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65929/638968a9-a2a3-3da8-3f99-844d13f4ce82.png)

ビルドが実行され、問題なければ以下のようなサンプルページが表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65929/ee642bce-0b03-0141-0e5e-b2fe1a9bbd21.png)

### roscoreとの通信

roscoreと通信するための環境は整ったので以下のようにコードを修正して接続させてみる。

(1) @types/roslib のインストール

これをインストールしないとビルド時のエラーが消えないので追加。

```bash:install
$ npm install @types/roslib
```

(2) App.vueの変更

サンプルのHelloWorld を削除してROS Topic を受信するコンポーネントを追加し変更。

```diff:App.vue
@@ -1,17 +1,16 @@
 <template>
   <div id="app">
-    <img alt="Vue logo" src="./assets/logo.png">
-    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
+    <RosClient/>
   </div>
 </template>

 <script lang="ts">
 import { Component, Vue } from 'vue-property-decorator';
-import HelloWorld from './components/HelloWorld.vue';
+import RosClient from './components/RosClient.vue';

 @Component({
   components: {
-    HelloWorld,
+    RosClient,
   },
 })
 export default class App extends Vue {}
```

```vue:RosClient.vue
<template>
  <div class="ros">
    <h1>Received topic: {{ topic }}</h1>
    <button v-on:click="pub">BUTTON</button>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { Ros, Topic, Message } from 'roslib'

@Component
export default class RosClient extends Vue {
  private topic: string = 'unkown';
  private ros: Ros;
  private bridge_server_uri: string;

  constructor() {
    super();
    this.bridge_server_uri = '<<slave pc IP>>';
    this.ros = new Ros({});
  }

  private mounted() {
    console.log('mounted');
    const uri = 'ws://' + this.bridge_server_uri + ':<<rosbridge_server port>>';
    try {
      this.ros.connect(uri);
      this.ros.on('connection', () => {
        console.log('[INFO]: connection');
      });

      this.ros.on('error', err  => {
        console.log('[ERROR]: ' + err);
      });

      this.ros.on('close', () => {
        console.log('[INFO]: close');
      });

      this.start_listen();

    } catch (e) {
      console.log('[ERROR]: ' + e);
    }
  }

  private start_listen() {
    const listener = new Topic({
      ros: this.ros,
      name: '/example',
      messageType: 'std_msgs/Int32'});

    listener.subscribe(message => {
      console.log('[INFO]: topic: ' + listener.name + ', payload: ' + message.data);
      this.topic = listener.name;
    });
  }

  private pub() {
    const topic = new Topic({
      ros: this.ros,
      name: '/example',
      messageType: 'std_msgs/Int32'});

    const msg = new Message({
                  data: 1234
                });
    topic.publish(msg);

  }
}
</script>
```

以下のようにボタンを押下して受信したtopic名が画面上に表示されれば成功です。

![rostopic.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65929/be0b6063-923b-1c79-2b48-25720fe683e4.gif)


その他モロモロは[docker環境](https://github.com/shun/docker-ros-mqtt-topic-example)を参照。
長くなったので、MQTTの実装は[その２](https://qiita.com/shun_xx/items/dcea947b14f9a632bbdf)に分けて記事にしたいと思います。