LINE Clova を使って怠惰なDrink Bar を開発する

# この記事の概要

この記事では、6/25に開催された「[スマートスピーカーを遊びたおす会 vol.6](https://kotodama.connpass.com/event/132347/)」の登壇ネタとして開発した『LINE Clova Drink Bar』のコンセプトや実装内容について解説します。

また、LINE Clova Drink Bar は[ヒーローズ・リーグのVUI 部門決勝](https://mashupawards.connpass.com/event/153888/)にノミネートいただきました。ありがとうございました。


# LINE Clova Drink Bar のコンセプト

LINE Clova Drink Bar（以下、Drink Bar）は、「怠惰」を目指した作品です。
VUI として、Drink Bar というサービスをどこまで人の手を使わずに声だけで操作できるかを試しています。

単純に指示したドリンクを抽出するだけはなく、怠惰のためには人間からの指示を状況に応じて置き換えることもあります。


# 出来ること

## 音声操作で好みのドリンクを抽出

これは単純に指示されたドリンクを抽出する機能です。
カスタムスロット「DRINK」として麦茶やオレンジジュースなどをスロットタイプに追加しておき、Clova で認識できるようにしておきます。

- お茶・ジュースなど複数から選択できる
    - スロット値のドリンク（麦茶、コーラなど）に応じ、抽出するドリンクを切り替える

## 同種のドリンクで代替して抽出

Drink Bar とはいえ在庫がないドリンクもあります。そんな時でも変わりのドリンクを提供するための機能です。
予めドリンクをグルーピング（麦茶、緑茶などはTEAグループ、オレンジジュース、りんごジュースはSOFTDRINKグループ など）しておき、在庫がないドリンクを指示されても同じグループ（同種）で在庫があるドリンクを抽出する機能です。

例えば、以下のように置き換えます。

- 緑茶が在庫に無ければ、同じお茶で在庫のある麦茶を抽出する
- 同様にオレンジジュースが無ければ、りんごジュースを抽出する

## 市販の銘柄でも認識可能

もう一つの置き換え機能が、市販されている商品名でもドリンクとして認識できるものです。
コーラを飲みたいと頭で考えていても、思わず「ペプシをちょうだい」と指示してしまうこともあります。そんな時にこの銘柄認識機能が役に立ちます。

例えば、以下のように認識します。

- 緑茶として認識する語句
    - 伊右衛門、生茶、綾鷹
- オレンジジュースとして認識する語句
    - なっちゃんオレンジ、つぶつぶみかん
- コーラとして認識する語句
    - ペプシ、ドクターペッパー、メッツコーラ

# デモ動画

言葉で説明しても難しいので、デモ動画を見ていただければ分かっていただけると思います。

[![Clovaスキルで怠惰にドリンクバーを操作する_20190801.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154381/4da79f0d-5a35-2e2d-314a-151cbb519f7a.png)](https://vimeo.com/371067704)



# システム構成

Drink Bar のシステム構成です。
スキル側はnode.js で実装し、ngrok で開発PC をサーバーとして動かしています。

![システム構成01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154381/d3199564-e680-02de-fad2-99a017b5f133.png)

実際にドリンクを抽出するハードウェア部分はobniz とエアーポンプ（DC モーター）、シリコンチューブを組み合わせた構成となっています。

![システム構成02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154381/cea18bca-e0ba-555e-d510-f233bd14b019.png)


# スキルの実装はシンプル

スキル部分はとてもシンプルです。
カスタムインテントは、飲みたいドリンクを指示する「DispenseDrinkIntent」のみです。

DispenseDrinkIntent では「麦茶をちょうだい」「オレンジジュースをいれて」などの指示を受け付けます。
あとは認識したドリンクを基に、JSON で定義しておいたDrinkModel で在庫有無や、どのエアーポンプを動かせば抽出されるかの情報を取得します。
在庫があればそのまま、なければ同種で在庫があるドリンクに置き換えて、抽出するためのエアーポンプを動作させるだけです。

```javascript:clova.js（抜粋）
    let messageText = '';
    let d = findDrinkById(drink);
    if (d['available'] === true) {
        // ドリンクバーに設置されているドリンクの場合
        messageText = `わかりました。${d['name']} をついでおくね！`
    } else {
        let sameTypeDrink = findSameTypeDrinkModel(d['type']);
        if (sameTypeDrink) {
            // 同じ種類のドリンクが設置されている場合はそちらを注ぐ
            messageText = `おっと、${d['name']} が無かったので、同じ種類の${sameTypeDrink['name']}をついでおくね！`;
        } else {
            // ドリンクが設置されてない場合は断りのメッセージを返す
            messageText = `ごめんなさい。${d['name']}を用意してなかったよ。他の飲みたいドリンクを教えてね。`;
        }
        d = sameTypeDrink;
    }
    // Clova のセリフを組み立てる
    const speechList = [];
    // 注ぐセリフ
    speechList.push(
        clova.SpeechBuilder.createSpeechText(messageText)
    );
```

```json:DrinkModel.json（抜粋）
{
  "list": [
    {
      "available": false,
      "id": "OolongTea",
      "name": "ウーロン茶",
      "slot": 0,
      "type": "TEA"
    },
    {
      "available": false,
      "id": "GreenTea",
      "name": "緑茶",
      "slot": 0,
      "type": "TEA"
    },
    {
      "available": true,
      "id": "BarleyTea",
      "name": "麦茶",
      "slot": 0,
      "type": "TEA"
    },
    {
      "available": false,
      "id": "BrownRiceTea",
      "name": "玄米茶",
      "slot": 0,
      "type": "TEA"
    },
    {
      "available": false,
      "id": "JasmineTea",
      "name": "ジャスミン茶",
      "slot": 0,
      "type": "TEA"
    },
    {
      "available": true,
      "id": "OrangeJuice",
      "name": "オレンジジュース",
      "slot": 1,
      "type": "SOFTDRINK"
    },
    {
      "available": false,
      "id": "AppleJuice",
      "name": "リンゴジュース",
      "slot": 1,
      "type": "SOFTDRINK"
    },
...後略
```

# 市販銘柄の認識は同義語をひたすら登録する

出来ることで挙げていた「市販の銘柄でも認識可能」ですが、こちらはカスタムスロットの同義語としてひたすら登録します。

![スクリーンショット 2019-12-12 15.21.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154381/8c6b39f4-3a95-de56-5e6b-412b3c5599f1.png)

怠惰のためには地道な作業もこなします。
ひたすら画面に入力するのも大変なので、Google スプレッドシートなどを活用してTSV ファイルとして出力し、Clova Developer Center のアップロード機能を活用すると少し楽ができます。

![スクリーンショット 2019-12-12 15.24.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154381/76f3b029-ef4d-abd1-3867-29a0d83663ce.png)

### アップロードファイル例

```tsv
OolongTea	ウーロン茶	烏龍茶	うーろん茶	黒烏龍茶																			
GreenTea	緑茶	りょくちゃ	おちゃ	お茶	伊右衛門	いえもん	綾鷹	あやたか	生茶	なまちゃ	おーいお茶	贅沢緑茶	特茶	ヘルシア緑茶	濃い茶								
BarleyTea	麦茶	むぎちゃ	ミネラル麦茶	ゴマ麦茶	胡麻麦茶	むぎ茶	健康ミネラル麦茶	六畳麦茶	六条麦茶	やさしい麦茶	香り薫るむぎ茶												
BrownRiceTea	玄米茶	げんまい茶	げんまいちゃ																				
JasmineTea	ジャスミン茶	ジャスミンティー	ジャスミン
```


## アップロード機能はサンプル発話の登録にも有効

アップロード機能はサンプル発話の登録にも有効です。
サンプル発話のテンプレートを決めて、そこにタグとドリンク名を連結してサンプル発話の文字列を一挙に生成します。

### アップロードファイル例

```
[INTENT SLOT]
Drink	DRINK

[INTENT EXPRESSION]
<Drink>ウーロン茶</Drink>を頂戴
<Drink>烏龍茶</Drink>を頂戴
<Drink>うーろん茶</Drink>を頂戴
<Drink>黒烏龍茶</Drink>を頂戴
<Drink>緑茶</Drink>を頂戴
<Drink>りょくちゃ</Drink>を頂戴
<Drink>おちゃ</Drink>を頂戴
<Drink>お茶</Drink>を頂戴
<Drink>伊右衛門</Drink>を頂戴
<Drink>いえもん</Drink>を頂戴
<Drink>綾鷹</Drink>を頂戴
<Drink>あやたか</Drink>を頂戴
<Drink>生茶</Drink>を頂戴
<Drink>なまちゃ</Drink>を頂戴
<Drink>おーいお茶</Drink>を頂戴
<Drink>贅沢緑茶</Drink>を頂戴
<Drink>特茶</Drink>を頂戴
<Drink>ヘルシア緑茶</Drink>を頂戴
```


# obniz を使ってドリンクを抽出する

ハードウェア構成もシンプルです。まずはobniz とエアーポンプを繋ぎます。
エアーポンプに繋いだシリコンチューブと、ドリンクが出てくる側のシリコンチューブをPET ボトルキャップに接続します。接続できるようにPET ボトルキャップに電気ドリルで穴を開けています。
うまくドリンクを抽出するポイントはボトルの気密を保つことです。チューブ外径にぴったりな穴が開けられない場合は、ホットボンドで穴とチューブを接着したり、キャップにパッキンを装着するなどして気密を保ってください。

![システム構成02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154381/cea18bca-e0ba-555e-d510-f233bd14b019.png)

以上で実装は終わりです。
とてもシンプルな作りとなっています。あとは外装などにこだわるのも良いですね。


# まとめ

実はこの「LINE Clova Drink Bar」は最初から作ろうと思っていたのではなく、別で作成していた「LINE Things Drink Bar」というLINE Things とLINE Pay を組み合わせた作品のスピンオフとして生まれた作品でした。
もう少し明かすと、「スマートスピーカーを遊びたおす会 vol.6」の登壇ネタに困っていた筆者が、時間かけずに作れるネタがないものか、という怠惰な発想から生まれた怠惰な作品なのでした。
実際の実装時間も半日程度です。怠惰ですませたいという気持ちを持っていると、作業効率の良い作品が生まれるのかもしれません。

そんな怠惰な作品が日本最大級のスマートスピーカーコミュニティでの登壇ネタとなり、日本最大級の開発コンテストの部門決勝にノミネートされてしまい、とても恐縮です。
あの有名なミステリーの女王 アガサ・クリスティが「発明は怠惰から生まれる」と言っていたように、怠惰にしたいがために動いていれば良い作品が生まれるのかもしれませんね。