Angularでroslibjsを動かす

#目次
[1.概要](#概要)
[2.環境](#環境)
[3.Angular側の導入](#angular側の導入)
[4.ROS側の導入](#ros側の導入)
[5.実践](#実践)
[6.おわりに](#おわりに)
#概要
今年の[ROS Advent Calendar](https://qiita.com/advent-calendar/2021/ros)にはROSとWebを連携する系の記事が多くあって面白いな〜と思って見ていたのですが、
VueやReactとROSの連携の記事はあるのに、AngularとROSについての記事って殆どないな…ということに気づきました。
そこで、本記事ではAngularを使ってROSと通信するところまで簡単に記載させていただきます。
ここが間違ってるという点があれば、見つけ次第コメントしていただけるとありがたいです。

#環境
* Ubuntu 20.04
* ROS 1 noetic
* node v16.10.0
* Angular 13

#Angular側の導入

### 前提
Angular,ROSの環境構築はすでに済んでいる仮定で進めます。
また、これから先はAngularで作成したプロジェクト内で行うものとします。

##1. roslibjsを導入
ROSと通信するためのライブラリである[roslibjs](https://github.com/RobotWebTools/roslibjs)を導入します。

```
npm install roslib
```
## 2. 型定義をインストール
roslibjsは名前の通りJavaScriptで使う用なので、TypeScriptでも使えるように型定義ファイルをインストールします。

```
npm i @types/roslib --save-dev
```

##3. 依存関係のインストール
roslibjsは多くのライブラリに依存しているので、それらをインストールします。

```
npm install
```

##4. WebWorkifyの修正

この部分は@shiro-kuma様の[ReactでROSのtopicをリアルタイム描画しながらフロントに入門する](https://qiita.com/shirokuma/items/2a58c7581b1bd129fd4c)を参考にさせていただきました。ありがとうございます。


Angular12からはWebpackが5になりましたが、[ここ](https://github.com/RobotWebTools/roslibjs/issues/382)で書かれているようにWebpack5とroslibjsは相性が悪いようです。
具体的にはWebpack5に含まれるWebworkifyの不具合のようですので、該当箇所を[Webworkifyのissue](https://github.com/browserify/webworkify/issues/43)を参考に修正します。

まずはAngularプロジェクト内の`node_modules/webworkify/index.js`を開きます。
そして、

```JavaScript:node_modules/webworkify/index.js(before)
 var bundleFn = arguments[3];
 var sources = arguments[4];
 var cache = arguments[5];
 var stringify = JSON.stringify;
module.exports = function (fn, options) {  
```
となっているのを、

```JavaScript:node_modules/webworkify/index.js(after)
var stringify = JSON.stringify;

module.exports = function (fn, options) {
    var bundleFn = arguments[3];
    var sources = arguments[4];
    var cache = arguments[5];
```
のように変更します。

これでAngular側の導入は完了です。



#ROS側の導入
## ros-bridge-serverをインストール

http://wiki.ros.org/rosbridge_suite
ここを参考に、ros-bridge-serverをインストールします。これはROSにwebsocketという通信方式を使ってアクセスできるようにするパッケージです。

```
sudo apt-get install ros-noetic-rosbridge-server
```
ROS側の導入は以上です。

# 実践
とりあえずComponentを1つ、Serviceを1つ作ってください。
Service側はこんな感じで書きます。
##Service

```TypeScript:ros.service
import { Injectable } from '@angular/core';
import * as ROSLIB from 'roslib';
import { Subject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class RosService {
  ros: ROSLIB.Ros;
  url = 'ws://127.0.0.1:9090';
  isConnected: Subject<boolean> = new Subject();
  listener: ROSLIB.Topic;
  talker: ROSLIB.Topic;

  constructor() {
    this.ros = new ROSLIB.Ros({
      url: this.url,
    });
    this.ros.on('connection', () => {
      console.log('Connected');
      this.isConnected.next(true);
    });

    this.ros.on('error', (error) => {
      console.log('error', error);
      this.isConnected.next(false);
    });

    this.ros.on('close', () => {
      console.log('closed');
      this.isConnected.next(false);
    });

    this.listener = new ROSLIB.Topic({
      ros: this.ros,
      name: '/listener',
      messageType: 'std_msgs/String',
    });

    this.talker = new ROSLIB.Topic({
      ros: this.ros,
      name: '/talker',
      messageType: 'std_msgs/String',
    });
  }
}

```

コンストラクタで`ROSLIB.Ros`オブジェクトを作成します。このオブジェクトは1つしか必要ないので使い回せるようにServiceで宣言します。
また、SubScribe/Publishするときに使う`ROSLIB.Topic`オブジェクトもここで生成します。
次にコンポーネントはこんな感じで書きます。

##Component

```TypeScript:top.component
import { Component, OnInit } from '@angular/core';
import * as ROSLIB from 'roslib';
import { RosService } from '../ros.service';

export interface String {
  data: string;
}

@Component({
  selector: 'app-top',
  templateUrl: './top.component.html',
  styleUrls: ['./top.component.scss'],
})
export class TopComponent implements OnInit {
  isConnected: boolean = false;
  message: string = '';
  constructor(private rosService: RosService) {}

  ngOnInit(): void {
    this.rosService.isConnected.subscribe(
      (data: boolean) => (this.isConnected = data)
    );
    this.rosService.listener.subscribe((msg: ROSLIB.Message) => {
      this.message = (msg as String).data;
    });
  }

  talk(message: any): void {
    const publishMsg: String = { data: message };
    this.rosService.talker.publish(publishMsg);
  }
}

```
名前がtop.componentというよくわからない名前になってるのは気にしないでください笑
コンポーネントでは主にデータの表示とpublishを行います。
注意してほしいのは、ROSの`std_msgs/String`型は存在しないので、

```TypeScript:String型
export interface String {
  data: string;
}
```
のようにして自分で型を定義してあげるところです。
最後にHTMLです。
##HTML

```HTML:top.component.html
<h1>ROS connected: {{ isConnected }}</h1>
<h1>Subscribed Message:{{ message }}</h1>
<label>
  publish message:
  <input type="text" #msg />
</label>
<button (click)="talk(msg.value)">publish</button>

```
HTMLはとりあえずシンプルに書きました。入力欄に文字を入れて`publish`ボタンを押すとpublishされるようになっています。


##実際の画面
![Screenshot from 2021-12-15 19-36-21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017430/61f1ddac-d5da-ce75-0118-473344774e66.png)

##実行
ターミナルを開いて、

```
  roslaunch rosbridge_server rosbridge_websocket.launch
```
と入力するとサーバーが立ち上がり、Webサイトと通信を開始します。Webサイト上で
`ROS connected:true`となれば接続完了です。

##Subscribe
さっきとは別のターミナルで

```
 rostopic pub /listener std_msgs/String "data: 'hello world'" 
```
と入力してください。Webサイト上の
`Subscribed Message`に`hello world!`が表示されればSubscribeしています。

##Publish
また別のターミナルで

```
rostopic echo /talker
```
と入力してください。`/talker`トピックの受信待ちをします。
次に、webサイト上で適当に文字を入力してpublishボタンを押してください。
僕は`Hello ROS!`と入力しました。
すると、ターミナルでは以下のように表示されます。
![モザイク.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2017430/c008ef3c-765a-e5a1-3221-12ad1603b918.png)

ちゃんとPublishできていることがわかります。


#おわりに
とりあえず簡単にAngular上でrosとpub/sub通信するプログラムを作ることができました。ネットではAngularとROSで連携する記事は本当に少ないので、困っている人の手助けになれば幸いです。

#参考文献
* [ReactでROSのtopicをリアルタイム描画しながらフロントに入門する](https://qiita.com/shirokuma/items/2a58c7581b1bd129fd4c)
* [Roslib with Angular 10](https://stackoverflow.com/questions/64647388/roslib-with-angular-10?answertab=active#tab-top)
* [roslibjsの基本的な使い方](https://qiita.com/daigakupotato/items/bb44c070d4304caa4bf1)