【2019年12月版】ラズパイDJにもっとリスナーの声を届けたい！【Angularで音声認識！】

[株式会社ピーアールオー（あったらいいな！を作ります） Advent Calendar 2019 の25日目](https://qiita.com/advent-calendar/2019/pro-japan1)。2019年のアドベントカレンダーの*フィナーレを飾る*、筆者の**マジであったらいいな！**はこちら〜！

# リスナーの声を届けたい！ → DJに"声"でリクエストしてみよう！

前回の記事での DJ-Bot は Bot と言いつつ固定のキーワードしか持てないのでリスナーの"声"が届かないDJなのですよね・・・

ぢゃ〜、Chrome搭載の**音声認識API**を使って、リスナーの"声"を**ガチでラズパイDJに届けて**みましょうか！(マジすか？！)

ということで…今回はAngularにて、"マイクからの音声を文字列に変換して送信する"リクエストフォームを作成いたします。
さらにこれをNode-REDにそのまま載せてみたいと思います。

## 1. Node-RED を SSL 対応 & 静的サイト有効化

まずはNode-REDで静的なコンテンツを載せられるように設定を変更します。
またChromeでマイクを使うには HTTPS がマストなので Node-RED にSSL対応を施します。

### 1-1. 静的コンテンツの有効化

まずは以下のサイトを参考に Node-REDのstatic fileを有効にしましょう。

- [Node-REDでWebページを作ろう](https://tibbo-pi.co-works.co.jp/blog/node-red%E3%81%A7web%E3%83%9A%E3%83%BC%E3%82%B8%E3%82%92%E4%BD%9C%E3%82%8D%E3%81%86/)

```js:~/.node-red/settings.js
...
    httpStatic: '/home/pi/node-red-static/',
...
```
これは、以上でOKです。

### 1-2. SSL証明書作成でHTTPS化

まず以下のページを参考に、普通のApache と同様の手順でオレオレSSL証明書を作成しましょう。

- [RaspberryPiでApache2をHTTPSに対応させたい！](https://qiita.com/utti0307/items/3fc7418a8832c3e99ae4)

```
$ mkdir ~/.node-red/ssl
$ cd ~/.node-red/ssl
$ sudo openssl genrsa -des3 -out server.key 2048
...
(パスワードを入力)
...
$ sudo openssl rsa -in server.key -out server.key
...
(パスワードを再度、入力)
...
$ sudo openssl req -new -days 3650 -key server.key -out server.csr
...
$ sudo openssl x509 -in server.csr -out server.crt -req -signkey server.key -days 3650
$ sudo chown pi:pi *
$ chmod 400 *
```
で、`~/.node-red/ssl`以下にSSL証明書が作成されました。ちなみに最初の`2048`ですが、手順では`1024`となっています。現在では暗号化の強度が足りない？とのことでNode-RED起動時にエラーとなってしまいます。

- [Running node-red-start after trying to create HTTPS “Error 140AB18F:SSL routines:SSL_CTX_use_certificate:ee key too small”](https://stackoverflow.com/questions/58263106/running-node-red-start-after-trying-to-create-https-error-140ab18fssl-routines)

Node-REDの設定は以下の手順で行います。

- [ローカルのNode-REDにhttpsアクセスする方法](http://blue.hatenadiary.jp/entry/2015/10/16/111405)

```js:~/.node-red/settings.js
...
var fs = require("fs");
...
    https: {
       key: fs.readFileSync('.node-red/ssl/server.key'),
       cert: fs.readFileSync('.node-red/ssl/server.crt')
    },
...
```
`fs`モジュールを有効にして `server.key`と`server.crt`を読み込んでいます。

これで `node-red-start` でNode-REDを起動し、`https://xxxx~`でアクセスできれば設定完了です。

## 2. Angular CLI をセットアップ

Angular の CLI ですが、こいつをそのまま `npm install -g` するのは[憲法違反](https://qiita.com/lambda_funtaro/items/865f178628a035acbe5a)なので(ラズパイの場合は`sudo`が必要でややこしいことになる)、`nvm` から `npm i -g` します。

```shell
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash
...
```
で、ターミナルを開き直して、

```
$ nvm install 10
...
```
node.jsのインストールが終わればもう使えるようになっておりますので、そのまま`@angular/cli`をインストールいたします。

```shell
$ npm i -g @angular/cli
```
ラズパイだけの環境だし、`-g`してもいいよね？！

それではアプリを生成します。

```
$ pwd
/home/pi
$ ng new DjRequestApp
$ cd DjRequestApp
```
はい、ラズパイだからってホームディレクトリ直下でアプリを生成してます。
Angularのビルド先を Node-RED の下に向けます。

```json:angular.json
...
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "../node-red-static/DjRequestApp",
...
```
そして忘れてはいけないのが、`index.html`の`<base>`です。
今回は `https://192.168.x.x:1880/DjRequestApp/`以下のような位置にAngularを配置しますので、`<base href="/DjRequestApp/">` と修正します。

一旦、Angularのソースは弄らずに、このままデプロイしてみてNode-REDの設定もちゃんとできているか確認してみましょう。

```shell
$ npm run build --prod
```
でビルド＆デプロイします。
別のターミナルで Node-REDを起動しておきます。

```shell
$ node-red-start
```
ブラウザでアクセスしてみましょう。

![DjRequestApp_Start.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/e5558958-072e-4810-fc0c-5aab885fb30e.png)

はい、Anuglarの初期ページが無事に表示されました。引き続き実装に参りましょう！！

## 3. Angular の実装

以下の音声認識APIをインストールいたします。

- [Angularで音声認識Serviceをつくった](http://blog.kamiazya.tech/post/ngx-speach-recognition/)
- [@kamiazya/ngx-speech-recognition | npm](https://www.npmjs.com/package/@kamiazya/ngx-speech-recognition)

```shell
$ npm i @kamiazya/ngx-speech-recognition
```
それではサンプルをほぼコピペしてみます。
まずはHTMLですが…

```html:app.component.html
<p>DJ Requested: {{message}}</p>
<button
  [disabled]="service.started$ | async"
  (click)="listen()"
>listen</button>
```
ボタン押されたら`listen()`が呼ばれるシンプルなフォーム？です。
続いてコンポーネント本体です。

```ts:app.component.ts
import { Component } from '@angular/core';

import {
  RxSpeechRecognitionService,
  resultList,
} from '@kamiazya/ngx-speech-recognition';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  providers:[
    RxSpeechRecognitionService
  ]
})
export class AppComponent {
  message = '';

  constructor(
    public service: RxSpeechRecognitionService,
    private http: HttpClient
  ) { }
 
  listen() {
    this.service
      .listen()
      .pipe(resultList)
      .subscribe((list: SpeechRecognitionResultList) => {
        this.message = list.item(0).item(0).transcript;
        console.log('RxComponent:onresult', this.message, list);
      });
  }
}
```
これだで本当に音声認識、できるのでしょうか・・・？！

## 4. 果たして本当にマイクから音声認識できるのか？！

いったん、ビルドをかけて Node-RED に載せてみましょう。

```shell
$ npm run build --prod
```
で、Chromeリロードしてみます。"サイトの設定"でマイクがブロックされていたら"確認"か"許可"をお願いします。

![Chromeサイト設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/b3040892-c6db-8a5f-a798-b7ea6ffb3a86.png)

`Listen`ボタンをクリックするとマイクの許可を求められるかもしれませんが、"許可"をお願いします！
スクショがうまく撮れませんでしたがChromeのタブが**録音中**っぽくなっているかと思います。
その間にリクエストする冬っぽいキーワードを呟いてみます。

![DjRequestApp_First_スキー (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/9020c019-434b-0dc1-7d22-06d689777add.png)

おぉ！！！！認識してるぅぅ〜！！！
うまく `message` に認識できた文字列が入ってきました。
後はこれを NODE-Red にPOSTすれば**DJに声が届きます**ね！！！

Angularの`HttpClient`を使用して、`message`をPOSTするようにコンポーネントを修正しましょう。

HttpClientの使い方はお決まりですが、まずは `app.module.ts`で `imports`にモジュールを追加します。

```ts:app.module.ts
import { HttpClientModule } from '@angular/common/http';
...
  imports: [
    BrowserModule,
    HttpClientModule
  ],
...
```
確かBrowserModuleの後、という決まりもあったような気がします。続いて、コンポーネント側でインジェクトしてもらいます。

```ts:app.component.ts
import { HttpClient } from '@angular/common/http';
...
  constructor(
    public service: RxSpeechRecognitionService,
    private http: HttpClient
  ) { }

  listen() {
    this.service
      .listen()
      .pipe(resultList)
      .subscribe((list: SpeechRecognitionResultList) => {
        this.message = list.item(0).item(0).transcript;
        console.log('RxComponent:onresult', this.message, list);
        this.http.post("/DjRequest",this.message)
          .subscribe(()=>{});
      });
  }
}
```
`this.http.post("/DjRequest",this.message).subscribe(...);`を追加いたしました。
NODE-Redで用意するエンドポイントは `/DjRequest` とします。

## 5. REST エンドポイントの用意

POSTの用意が出来たら Node-RED 側でエンドポイントを作成します。

"http in"ノードを置いて、メソッドに`POST`、URLに`/DjRequest` を指定します。

![Node-RED_HTTP_REQUEST_POST.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/da544b1d-16d2-3ffb-8164-1e64ca006b58.png)

基本はこれだけなのですが、戻り値を作成する "function"ノードと"http response"ノードも配置します。

functionノードは以下の通り、OKメッセージを送るだけです。

```js
msg.payload = { message: 'ok' }

return msg;
```
このメッセージをステータス`200`で送り返せば、REST APIは完了です。

![Node-RED http response.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/38f954cd-25e7-a0cc-5283-f5450254c74b.png)

さて、デバッグノードを付けて、Angularから試しにPOSTしてみましょう！

```shell
$ npm run build --prod
```
で、いくつか呟いてみた結果が以下です。

![Node-RED_Request_Debug.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/5eaddde7-568b-60f7-b76c-5527b6c4233f.png)

バッチリ、POSTされています。つ、ついに…

## 6. 接続＆実行！

AnguarとNode-RED の疎通ができたようなので、こいつをパイプで結びます！

最後に、リクエストの`body`から `payload`にメッセージを載せる "function"ノードを設置して完了です。

```js
var message = msg.req.body
msg.payload = message
return msg;
```
これでREST APIにPOSTされたメッセージが `req.body`から`payload`に載ります。後は[前回の記事](https://qiita.com/koinori/items/5bf1e1f63b693d7e8a46)の"プレイリストの検索"のパイプラインに繋げれば・・・OKです！！

全体としては以下のようになりました。

![Node-RED_全体像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/d32938e0-b3cb-a457-0f54-02774672dfcf.png)

これで準備は整いました。

さぁ、声が届くか！？
![スクリーンショット DJ Request パーティー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/da79ccf6-c4fa-ac92-0e16-85005e9a11cf.png)
無事に ラズパイDJ に `パーティー`の一声が届いた模様です！！
それでは、冬といえばこれだ〜！！！
![スクリーンショット DJ リクエスト広瀬香美.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/8cb17a6b-1a89-fbaf-60f1-e1500c2f2b84.png)
ロマンスの神様 ♪───Ｏ（≧∇≦）Ｏ────♪
いや〜、ウケるw

## まとめ

駆け足ではございますが、以下のようなことを試してみました。

- Node-RED を HTTPS化
- Node-RED で静的コンテンツの有効化
- Node-RED で Anguarをホスティング
- Angular で音声認識
- Node-RED から Spotify API 呼び出し
- Raspberry Pi4 で Spotify Connect 再生

いや〜相変わらず技術多め濃いめの~~ウザ~~熱いクリスマスになりましたね〜！！
それではラズパイDJの🔊BGM🔊に乗って🎄パーティー🎄を始めましょうか！

(なんとか…無事に間に合ってよかった。。。)
![ルノアールで一服.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335150/4b35efde-c007-9d6a-258d-965ea9861157.png)