enebularのNode-RedからBOCCOを発話させる

# はじめに
enebularのNode-Redから文字を入力しBOCCOを発話させてみました。

[BOCCO](https://www.bocco.me/bocco/)とは、ユカイ工学から開発した家族をつなぐコミュニケーションロボットです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/589e210a-e0b2-e16e-9433-eab676ba67e1.png" width="300">

BOCCOに話しかけると、それが音声メッセージとして送信され、スマートフォンからはテキストか音声でメッセージを送信することができます。
子供、両親、祖父母などのスマートフォンの操作ができない家族とも簡単にコミュニケーションをとることができる、そんなコミュケーションロボットです。また、温度センサや振動センサ、鍵センサとの連携もできます。

またBOCCOには開発者向けに、BOCCOに自由な言葉を発話させたり、BOCCOに話しかけられた発話データを取得したりすることができるように[APIが公開](http://api-docs.bocco.me/)されています。

そのAPIを利用することNode-RedからBOCCOを発話させることができるので、その方法について説明します。
意外と、APIをたたくときにcurlコマンドからNode-Redへの置き換えに詰まってしまいました。
なので、本記事はBOCCO API以外のAPIをNode-Redから叩くときに参考になるかと思います。

本記事執筆にあたって[@1ft_seabass]（https://qiita.com/tseigo/lgtms)さんにとてもよく助けていただきました、ありがとうございます！！


# BOCCO APIをつかう前準備
BOCCO APIを使うためには下記の準備が必要です。
1. BOCCOを買う
2. BOCCOアプリのユーザーアカウントをつくる
3. [BOCCO APIの利用申請](http://api-docs.bocco.me/)を行い、APIキーを取得しておく

# 本記事のスコープ
本記事では、BOCCO APIで[メッセージを送信する](http://api-docs.bocco.me/post_message.html)部分の実装を書きます。なので、APIの[アクセストークンの取得](http://api-docs.bocco.me/get_access_token.html)や[チャットルームの取得](http://api-docs.bocco.me/get_joined_rooms.html)（発話させたいアプリ上のルームのuuid取得）は事前にcurlコマンドを通して行われているものとします。

# curlコマンドからNode-Redの置き換えを考える
BOCCOにメッセージを送信するためには、curlコマンドで下記のようにします。
※access_toke-xxxxxxxxxには、[「アクセストークンの取得」](http://api-docs.bocco.me/get_access_token.html)を参考にし取得したもの、room-id-xxxxxxxxxには、[「チャットルームの取得」](http://api-docs.bocco.me/get_joined_rooms.html)を参考にした文字列が入ります。

```bash

curl -i "https://api.bocco.me/alpha/rooms/room-id-xxxxxxxxx" \
    -d "access_token=access_toke-xxxxxxxxx" \
    -d "unique_id=`uuidgen`" \
    -d "media=text" \
    -d "text=こんにちはBOCCO！" \
    -H "Accept-Language: ja"
```

ぱっとみて、NOde-Redに置き換える上で疑問に思った点は下記の通りです

- （おそらくPOSTだと考えていたが、）POSTなのかどうかよくわからん
- uuidgenにはいるUUID version 4のNode-Red上での生成方法
- -dや-HのペイロードをNode-Red上でどう表記するか

よくよく調べると、[curlのオプション](https://qiita.com/ryuichi1208/items/e4e1b27ff7d54a66dcd9)から

- -d : POSTリクエストとしてフォームを送信
- -H : HEADER HTTPヘッダにHEADERを追加もしくは変更

を意味するので、 上記のcurlはPOSTで良さそうだということがわかりました。

# メッセージを送信するためのNode-Redのフロー
結果として、メッセージを送信するためのNode-Redのフローは下記のようになりました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/a086a721-8642-b794-07f6-6dd1c6cbfdbc.png)



```

[{"id":"a511b8e0.9cce78","type":"tab","label":"フロー 2","disabled":false,"info":""},{"id":"a1bd5804.e816f8","type":"inject","z":"a511b8e0.9cce78","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":200,"wires":[["5d0ef47f.4601fc"]]},{"id":"5d0ef47f.4601fc","type":"change","z":"a511b8e0.9cce78","name":"curl -d 部分","rules":[{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"payload.access_token","pt":"msg","to":"x2c4sjxacw4tq72q5h9vrsgjbcz3p55ixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","tot":"str"},{"t":"set","p":"payload.media","pt":"msg","to":"text","tot":"str"},{"t":"set","p":"payload.text","pt":"msg","to":"こんにちはBOCCO！","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":370,"y":200,"wires":[["fd594459.862968"]]},{"id":"1945a3cc.d39e4c","type":"change","z":"a511b8e0.9cce78","name":"curl -H 部分（ヘッダー）","rules":[{"t":"set","p":"headers.Accept-Language","pt":"msg","to":"ja","tot":"str"},{"t":"set","p":"headers.Content-Type","pt":"msg","to":"application/x-www-form-urlencoded","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":300,"wires":[["a41d5dc1.08334","e1875ceb.2a6d1"]]},{"id":"a41d5dc1.08334","type":"http request","z":"a511b8e0.9cce78","name":"","method":"POST","ret":"txt","paytoqs":true,"url":"https://api.bocco.me/alpha/rooms/E7607BA3-2AA0-4DEB-8959-XXXXXXXXXXXX/messages","tls":"","persist":false,"proxy":"","authType":"","x":550,"y":300,"wires":[["301fb11b.5e036e"]]},{"id":"301fb11b.5e036e","type":"debug","z":"a511b8e0.9cce78","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":770,"y":300,"wires":[]},{"id":"e1875ceb.2a6d1","type":"debug","z":"a511b8e0.9cce78","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":550,"y":380,"wires":[]},{"id":"fd594459.862968","type":"uuid","z":"a511b8e0.9cce78","uuidVersion":"v4","namespaceType":"","namespace":"","namespaceCustom":"","name":"payload.unique_id に uuid を付与","field":"payload.unique_id","fieldType":"msg","x":640,"y":200,"wires":[["1945a3cc.d39e4c"]]}]

```

一つ一つみていきます。
## injectノード(タイムスタンプ)
injectノード(タイムスタンプ)が、このフローの開始ボタンとなっています。
この左四角ボタンをおすとフローが開始されます。
注意する点としては、タイムスタンプは日時のペイロードをもち、次のノードへと流れていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/28dde489-60f3-5d1d-ae1f-f0966c7e5bc0.png)


## changeノード(curl -d 部分)
次のchangeノードではcurlコマンドでいう-dの部分を記述する役割を持っています。
changeノードは上から下へ処理が降りていきます。
なので最初の。msg.payloadに対して{}と代入しているのは、先ほどのinjectノード(タイムスタンプ)の日時のペイロードを初期化するためです。それ以下のpayloadはcurlコマンドの要素をそれぞれ記載しています。
「msg.payload.~~~」と記述します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/4a52d6f2-2129-e59d-8483-1835c03a6cd0.png)

## uuidノード（payload.unique_idにuuidを付与）
これはuuidを生成するノードです。
「パレットの管理」→「ノードを追加」で「uuid」と検索すると、「node-red-contrib-uuid」と出てくるので追加して使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/68609cd4-8d02-7074-d9ab-227e5c3f35ab.png)

こちらはcurlコマンドでいう「"unique_id=`uuidgen`"」に対応している部分となります。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/4859e01c-ff37-4923-c5a2-cc8e1e81f7b4.png)

## changeノード（curl -H）
こちらのノードにはcurlコマンドで「"Accept-Language: ja"」に対応します。
そして、このノードにはもう一つ重要なmsg.headerが入ります。
それが画像に記載のある「msg.headers.Content-Type」とその値「application/x-www-form-urlencoded」です。
これは盲点というか、全く未知のことでしたが、[@1ft_seabass]（https://qiita.com/tseigo/lgtms)さんにご指導いただき理解しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/79adadc1-9e73-db87-ed75-e8830897f193.png)

Node-Redではもともとjsonで送るようContent-Typeが「application/json」で指定されているようです。
なので、どのContent-Typeでおくるのか意識して記述する必要があります。

※調べていたら同じようなことで、詰まっていたかがいらっしゃいました。[Node-REDでA3RT APIを使うときのハマりどころ](https://qiita.com/07JP27/items/ee415ab07836d5ffbb95])

## http requestノード
こちらのノードでは「POST」を指定してURL部分はcurlコマンドで記述したURLを入れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/80e8c62e-27e0-80df-7baf-50a5f622ee0e.png)

# 動作確認
先ほどのノードを動かしてみると...喋った！！！！！！！BOCCCOがenebularのNode-Redから喋った！！！！！！！！
curlコマンド上のtextを変更すれば送信するメッセージを自由にかえることができます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">enebularのNode-RedからBOCCOを発話させてみました🤔<br>http requestのContentTypeの設定が肝だった、なるほどなぁ。<a href="https://twitter.com/1ft_seabass?ref_src=twsrc%5Etfw">@1ft_seabass</a> さんにめちゃくちゃ教えてもらいました🙏🙏🙏<a href="https://twitter.com/hashtag/enebular?src=hash&amp;ref_src=twsrc%5Etfw">#enebular</a> <a href="https://twitter.com/hashtag/NodeRed?src=hash&amp;ref_src=twsrc%5Etfw">#NodeRed</a> <a href="https://t.co/vmjQR4iq1n">pic.twitter.com/vmjQR4iq1n</a></p>&mdash; たくろーどん (@takudooon) <a href="https://twitter.com/takudooon/status/1333927859241488393?ref_src=twsrc%5Etfw">December 2, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


# 応用編：UIからテキストを入力してBOCCOを発話させる
全体のフローは下記の通りです。
先ほどのinjectノード（タイムスタンプ）部分がtext inputノードに置きえています。
主に変更が必要な3点のノードについて下記で解説します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/df1ceb77-1c67-9fed-a049-0d3c70bd751e.png)


```

[{"id":"fbe90451.f204e8","type":"tab","label":"フロー 2","disabled":false,"info":""},{"id":"f22ca949.cbcff8","type":"comment","z":"fbe90451.f204e8","name":"メッセージ送信","info":"","x":100,"y":80,"wires":[]},{"id":"c50ee121.d83cf","type":"change","z":"fbe90451.f204e8","name":"curl -H 部分","rules":[{"t":"set","p":"headers.Accept-Language","pt":"msg","to":"ja","tot":"str"},{"t":"set","p":"headers.Content-Type","pt":"msg","to":"application/x-www-form-urlencoded","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":280,"wires":[["1646e643.b3242a","7d781da3.478604"]]},{"id":"1646e643.b3242a","type":"http request","z":"fbe90451.f204e8","name":"","method":"POST","ret":"txt","paytoqs":true,"url":"https://api.bocco.me/alpha/rooms/xxxxxxxx/messages","tls":"","persist":false,"proxy":"","authType":"","x":310,"y":340,"wires":[["ba5cd2d2.6f1b5"]]},{"id":"ba5cd2d2.6f1b5","type":"debug","z":"fbe90451.f204e8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":510,"y":340,"wires":[]},{"id":"7d781da3.478604","type":"debug","z":"fbe90451.f204e8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":690,"y":280,"wires":[]},{"id":"d85b093f.57a428","type":"ui_text_input","z":"fbe90451.f204e8","name":"","label":"","tooltip":"","group":"f11ebb11.fd1b88","order":4,"width":0,"height":0,"passthru":true,"mode":"text","delay":"300","topic":"","x":80,"y":220,"wires":[["db7bd901.751608","99c90f9c.edaba"]]},{"id":"c21ab3a0.6b448","type":"debug","z":"fbe90451.f204e8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.text","targetType":"msg","x":490,"y":140,"wires":[]},{"id":"99c90f9c.edaba","type":"change","z":"fbe90451.f204e8","name":"msg.payload → msg.payload.text","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload.text","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":320,"y":220,"wires":[["c21ab3a0.6b448","18258676.518dea"]]},{"id":"db7bd901.751608","type":"debug","z":"fbe90451.f204e8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":270,"y":140,"wires":[]},{"id":"320fc2e2.485a5e","type":"change","z":"fbe90451.f204e8","name":"curl -d 部分","rules":[{"t":"set","p":"payload.access_token","pt":"msg","to":"xxxxxxxxxxx","tot":"str"},{"t":"set","p":"payload.media","pt":"msg","to":"text","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":280,"wires":[["c50ee121.d83cf"]]},{"id":"18258676.518dea","type":"uuid","z":"fbe90451.f204e8","uuidVersion":"v4","namespaceType":"","namespace":"","namespaceCustom":"","name":"payload.unique_id に uuid を付与","field":"payload.unique_id","fieldType":"msg","x":620,"y":220,"wires":[["320fc2e2.485a5e"]]},{"id":"f11ebb11.fd1b88","type":"ui_group","z":"","name":"text","tab":"a7ecc75a.8f0de8","order":1,"disp":true,"width":"6","collapse":false},{"id":"a7ecc75a.8f0de8","type":"ui_tab","z":"","name":"home","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

```

## text inputノード
こちらにはテキストを入力するためのノードになります。
入力されたテキストはmsg.payloadに格納されます。
またこちらノードはダッシュボードに追加しておきます。
text inputノードのプロパティから「Group」→「新規にui_groupに追加」でぽちぽち進めていきましょう。
そうすると、右のダッシュボード画面の「タブ&リンク」にui_groupが追加されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/b5a8c62f-2bda-3a34-706c-82c4fd1e84f4.png)

## changeノード（msg.payload → msg.payload.text）
先ほどのtext inputノードで入力されたテキストはmsg.payloadに格納されます。
なので、curlコマンドで渡されるように、msg.payload.textに変換して次のノードに渡します。
この時、「値の移動」を使って変換します。

changeノードの使い方は[こちらの記事](https://qiita.com/joeartsea/items/1be1b7f8d08fab04ca73)がわかりやすかったです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/158f54b4-0b0b-002e-db3b-d0b74cd78ffd.png)

## changeノード（curl -d 部分）
こちらのノードは下記のように設定します。
今回は初期化する必要はないのでmsg.payloadに{}は代入しません。
また、msg.payload.textはinput textノードから流れてくるので、ここには記述しません。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/331891d1-8d24-461b-40b4-d4b7a71f00e5.png)

## 動かしてみる
デプロイし、UIを表示させ、テキストを入力するとBOCCOが発話してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212276/a5a86ee4-eb84-9f14-5087-70c6a2e5db5b.png)


# おわりに
今回はenebularのNode-Redを使ってBOCCOを発話させてみました。
curlコマンドで記述されたAPIのPOSTをNode-Redに置き換える方法を理解することができました。
Node-Redのノードごとに流れていくmsg.payloadの動きを、ちゃんとdebugノードでデバッグメッセージで表示させて、確認することが重要だと感じました。
これで他のAPIもNode-Redから叩くことができそうです。