🤖

BOCCO emo をNode-REDから入力したテキストで発話させてみる

2021/10/08に公開

目的

ユカイ工学からコミュニケーションロボット「BOCCO」の後継機「BOCCO emo」が発売されています。そしてBOCCOと同様にBOCCO emoのAPI(BOCCO emo Platform API)が公開されています。公式ページによる下記のようなことできるそうです。

BOCCO emo Platform APIとは
BOCCO emoを、REST APIを通して制御することのできるAPIです。
本APIを利用することで、外部のプログラムからBOCCO emoを発話させたり、
BOCCO emoが検知したイベントを外部のプログラムで受け取ることができます。

  • 他のシステムのアクション結果を、BOCCO emoに発話させる
  • BOCCO emoが検知したイベントを、自作プログラムで受信する
  • BOCCO emoのモーションを作成して、オリジナルなふるまいをするBOCCO emoを作り上げる
    等のことが可能になります。

初代BOCCOよりエモーショナルなBOCCO emoと連携した何かをつくることができるので、テンション上がりますね。
そこで本記事では、Node-REDからお手軽にBOCCO emo Platform APIを使ってみたいと思います。

ゴール

本記事を読むことで、Node-REDのUIから入力したテキストをBOCCO emoに発話させる方法を理解できます。また、最初にこちらの初代BOCCOをNode-REDから発話させたチュートリアルを読んでおくと理解が捗ります。基本的なAPIの説明は公式ページをご確認ください。

BOCCO emo Platform APIをつかう上で注意すること

BOCCO emo Platform APIをつかうためにアクセストークンが必要です。
こちらのアクセストークンは1時間で有効期限切れとなるため、定期的に再取得しなければいけません。
そのためアカウントのダッシュボードからリフレッシュトークンを取得しておき、これをつかってアクセストークンを定期的に更新して利用すると良さそうです。長期的なシステムを使う場合は、アクセストークンの更新を考慮しておきましょう。本記事のNode-REDではリフレッシュトークンをつかって、アクセストークンを定期的に取得、グローバル変数に格納してそれぞれのノードで利用するようにしています。

アクセストークンの定期取得

下記がリフレッシュトークンをつかったアクセストークンの定期取得のフローになります。
こちらの公式ページの説明に従って実装します。

アクセストークンの定期取得のフロー
[{"id":"55d10d43.443754","type":"tab","label":"フロー 1","disabled":false,"info":""},{"id":"5eafc934.ac5ce8","type":"http request","z":"55d10d43.443754","name":"POST","method":"POST","ret":"obj","paytoqs":false,"url":"https://platform-api.bocco.me/oauth/token/refresh","tls":"","persist":false,"proxy":"","authType":"","x":570,"y":200,"wires":[["1fbb154b.05fc2b","cabbc063.e76e6","3e2532c5.c690de"]]},{"id":"e4065ab5.88d4b8","type":"inject","z":"55d10d43.443754","name":"","topic":"アクセストークンを定期的に取得","payload":"","payloadType":"str","repeat":"1800","crontab":"","once":true,"onceDelay":0.1,"x":220,"y":140,"wires":[["961c87ca.1ced18"]]},{"id":"1fbb154b.05fc2b","type":"debug","z":"55d10d43.443754","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.access_token","targetType":"msg","x":820,"y":240,"wires":[]},{"id":"6d7e2f2e.18bbe","type":"change","z":"55d10d43.443754","name":"リフレッシュトークン","rules":[{"t":"set","p":"payload","pt":"msg","to":"{   \"refresh_token\": \"リフレッシュトークン\" }","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":200,"wires":[["5eafc934.ac5ce8"]]},{"id":"961c87ca.1ced18","type":"change","z":"55d10d43.443754","name":"Content-Type","rules":[{"t":"set","p":"headers.Content-Type","pt":"msg","to":"application/json","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":180,"y":200,"wires":[["6d7e2f2e.18bbe"]]},{"id":"cabbc063.e76e6","type":"debug","z":"55d10d43.443754","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.refresh_token","targetType":"msg","x":820,"y":280,"wires":[]},{"id":"3e2532c5.c690de","type":"change","z":"55d10d43.443754","name":"グローバル変数に格納","rules":[{"t":"set","p":"access_token","pt":"global","to":"payload.access_token","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":800,"y":200,"wires":[[]]}]

「アクセストークンを定期的に取得」とあるinjectノードをある一定時間で繰り返し発火させることでアクセストークンを更新するようにしています。

そしてPOSTしてかえってきたアクセストークンをChangeノードを使ってNode-RED上のグローバル変数に格納します。

グローバル変数に格納されれば、画面右にある▼(プルダウンメニュー)のコンテキストデータから確認することができます。

アクセストークンの呼び出し

グローバル変数を利用したアクセストークンの呼び出しは下記のようなフローで行っています。

functionノードでglobal.get("access_token")とすることで格納した値を呼び出しています。
またAPIの仕様上、アクセストークンの先頭に「Bearer 」(半角空白あり)をつける必要があります。

curl "https://platform-api.bocco.me/v1/rooms" \
     -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

なので、文字列連結も同時に行っています。

そして、これをヘッダーに使用するので、changaノードで下記のように変換しています。

部屋一覧の取得

メッセージを投稿したい部屋のID(room_uuid)を調べます。
こちらも公式ページの説明に従ってフローをつくります。
部屋のIDもグローバル変数で管理し呼び出して使っても良いと思います。
本記事でのサンプルでは、デバッグメッセージで表示された部屋のIDをコピーしてURLに埋め込み使用しています。
※サンプルコードの入力箇所には【部屋のID】と記載しています。

部屋一覧の取得のフロー
[{"id":"893470f7.79e16","type":"tab","label":"フロー 1","disabled":false,"info":""},{"id":"ebca2efe.5ae0f","type":"http request","z":"893470f7.79e16","name":"","method":"GET","ret":"txt","paytoqs":false,"url":"https://platform-api.bocco.me/v1/rooms","tls":"","persist":false,"proxy":"","authType":"","x":670,"y":280,"wires":[["1c3d61d5.38ae7e"]]},{"id":"1c3d61d5.38ae7e","type":"debug","z":"893470f7.79e16","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":850,"y":280,"wires":[]},{"id":"61ed4d2c.d306a4","type":"inject","z":"893470f7.79e16","name":"","topic":"アカウント情報の取得","payload":"","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":210,"y":220,"wires":[["afdef5a4.6df058"]]},{"id":"afdef5a4.6df058","type":"function","z":"893470f7.79e16","name":"JSONのデータ作成","func":"//globa.get()でglobal変数の値を取得する\n//「Bearer 」を連結する(※空白も含める)\nmsg.payload = \"Bearer \" + global.get(\"access_token\");\n\n\nreturn msg;","outputs":1,"noerr":0,"x":470,"y":220,"wires":[["241f31d5.35bd9e"]]},{"id":"241f31d5.35bd9e","type":"change","z":"893470f7.79e16","name":"アクセストークンの指定","rules":[{"t":"set","p":"headers.Authorization","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":280,"wires":[["ebca2efe.5ae0f"]]}]

メッセージを送信

これまでに説明したことを踏まえて、仕様を確認しながらフローを実装します。

メッセージを送信のフロー
[{"id":"893470f7.79e16","type":"tab","label":"フロー 1","disabled":false,"info":""},{"id":"95ff51d0.06c3d","type":"http request","z":"893470f7.79e16","name":"","method":"POST","ret":"txt","paytoqs":false,"url":"https://platform-api.bocco.me/v1/rooms/【部屋のID】/messages/text","tls":"","persist":false,"proxy":"","authType":"","x":630,"y":220,"wires":[["4c8ab8d4.080258"]]},{"id":"b0dfbd2b.862d4","type":"inject","z":"893470f7.79e16","name":"","topic":"メッセージ送信","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":180,"y":140,"wires":[["2c568ecb.edf6e2"]]},{"id":"4c8ab8d4.080258","type":"debug","z":"893470f7.79e16","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":810,"y":220,"wires":[]},{"id":"aa4b5cac.92037","type":"change","z":"893470f7.79e16","name":"発話内容","rules":[{"t":"set","p":"payload","pt":"msg","to":"{   \"text\": \"おはよう。\" }","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":480,"y":220,"wires":[["95ff51d0.06c3d"]]},{"id":"e390d00a.befda","type":"change","z":"893470f7.79e16","name":"Content-Type","rules":[{"t":"set","p":"headers.Content-Type","pt":"msg","to":"application/json","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":320,"y":220,"wires":[["aa4b5cac.92037"]]},{"id":"2c568ecb.edf6e2","type":"function","z":"893470f7.79e16","name":"JSONのデータ作成","func":"msg.payload = \"Bearer \" + global.get(\"access_token\");\nreturn msg;\n","outputs":1,"noerr":0,"x":390,"y":140,"wires":[["4202031c.46725c"]]},{"id":"4202031c.46725c","type":"change","z":"893470f7.79e16","name":"アクセストークンの指定","rules":[{"t":"set","p":"headers.Authorization","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":630,"y":140,"wires":[["e390d00a.befda"]]}]

UIから任意のテキストを入力して発話させる

Node-REDのフローは下記のようになります。
実際はアクセストークンの定期更新を組み合わせて使います。
また部屋のIDはご自身のものを使用してください。

UIから任意のテキストを入力して発話させるフロー
[{"id":"be1234a0.aebea8","type":"tab","label":"フロー 1","disabled":false,"info":""},{"id":"f3115942.e274f8","type":"change","z":"be1234a0.aebea8","name":"Content-Type","rules":[{"t":"set","p":"headers.Content-Type","pt":"msg","to":"application/json","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":180,"y":220,"wires":[["22b39ce0.3efb64"]]},{"id":"22b39ce0.3efb64","type":"http request","z":"be1234a0.aebea8","name":"POST","method":"POST","ret":"txt","paytoqs":true,"url":"https://platform-api.bocco.me/v1/rooms/【部屋のID】/messages/text","tls":"","persist":false,"proxy":"","authType":"","x":350,"y":220,"wires":[["61559b62.8d16a4"]]},{"id":"61559b62.8d16a4","type":"debug","z":"be1234a0.aebea8","name":"結果","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":530,"y":220,"wires":[]},{"id":"b4a161ee.d9ee1","type":"ui_text_input","z":"be1234a0.aebea8","name":"BOCCO emo","label":"BOCCO emo","tooltip":"","group":"e29d570d.95fe98","order":4,"width":0,"height":0,"passthru":true,"mode":"text","delay":"0","topic":"","x":170,"y":120,"wires":[["287d5bfb.eb19e4"]]},{"id":"287d5bfb.eb19e4","type":"change","z":"be1234a0.aebea8","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":400,"y":120,"wires":[["2c34ae4a.8cc8b2"]]},{"id":"20958f79.73608","type":"comment","z":"be1234a0.aebea8","name":"発話内容をUIから入力する","info":"","x":210,"y":40,"wires":[]},{"id":"2c34ae4a.8cc8b2","type":"function","z":"be1234a0.aebea8","name":"JSONのデータ作成","func":"//globa.get()でglobal変数の値を取得する\nmsg.payload.Authorization = \"Bearer \" + global.get(\"access_token\");\nreturn msg;","outputs":1,"noerr":0,"x":650,"y":120,"wires":[["d47dedb1.e517c"]]},{"id":"d47dedb1.e517c","type":"change","z":"be1234a0.aebea8","name":"アクセストークンの指定","rules":[{"t":"set","p":"headers.Authorization","pt":"msg","to":"payload.Authorization","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":870,"y":120,"wires":[["f3115942.e274f8"]]},{"id":"e29d570d.95fe98","type":"ui_group","z":"","name":"text","tab":"2daee652.d9d5ca","order":1,"disp":true,"width":"6","collapse":false},{"id":"2daee652.d9d5ca","type":"ui_tab","z":"","name":"home","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

まず「BOCCO emo」と表示されたinputノードから発話させたメッセージをUIから入力します。
その後、changeノードでメッセージを送信するのに必要な形に変換しPOSTしています。

下記のような感じになります。
しゃ、喋った!

おわりに

無事、Node-REDのUIからBOCCO emoを発話させることができました。
他のAPIも試してみたいと思います。

Discussion