Amazon Echo Dot+さくらのクラウドでAlexaになんか喋らせる

# Amazon Echo Dot+さくらのクラウドでAlexaになんか喋らせる

あんまり買う気がないのにとりあえず招待メールを要求しておいたら発売日にメールが来たのでEcho Dotを買った。

スキルという形でAlexaにいろんな動作をさせられるらしい。AWS LambdaかHTTPSエンドポイントを与えるとJSONをやりとりして動作を行わせたり喋る内容を返したりする形っぽい。

みんなLambda使ってるけどあんまり使う気にならないので（政治的要因はないです）Pythonで待ち受けてなんかさせてみます。

私は社のタイムカードをSlack経由で打刻できるようにしており、Alexaで打刻できたらなんとなくナウいのでタイムカードスキルを作ってみることにします。

既にタイムカードをよろしくやるBotが稼働しているので、Slack上でBot同士を会話させてタイムカードをよろしくやってもらう方針とします。

# Lambdaを使わずにスキルを作るのに必要な物
* Amazon.co.jp もしくは .comのアカウント
* HTTPSで口を開けられるなんらかのサーバ（今回はUbuntu 16.04+さくらのクラウドを使用）
* 独自ドメイン（Let's Encryptで楽にやるなら）

基本的にはこれだけで、実はAlexaのスキルを作るのにEchoの実機は不要です。

ここで実機を持っている方に注意点ですが、[開発者コンソール](https://developer.amazon.com/edw/home.html#/)にはAmazon.co.jpとAmazon.comのどちらのアカウントでもログインすることができます。

これがどういうことかと言うと、**同じメールアドレスで.co.jpと.comの両方のアカウントを持っていた場合、どちらのアカウントで入るかを決める要素がパスワードしかありません。仮に.co.jpと.comで同じメールアドレス/パスワードを使用している場合には.com側のアカウントが優先されます。**

一見.comで入っても問題ないじゃん、と思うところですが実機を持っている場合には大きな問題があり、**何も考えずにEchoを.co.jpのアカウントに紐付けていると.comのアカウントで開発中のスキルを実機にデプロイできません。**
当然と言えば当然ですが、モデルを構築していざ実機でテスト、という段になって気付いて割と時間を無駄にしたのでメモ書きです。

# スキルの作り方
スキルの作り方は[Developer.IOさんに詳しく書かれています。](https://dev.classmethod.jp/etc/first-step-of-making-alexa-custom-skills/)
だいたいこの手順に従ってモデルのビルドまでを済ませます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/b031025f-8cc0-94ce-f991-73a8cbf37d65.png)


今回私はAlexaを用いて出勤したいので、スキルビルダーでは`タイムカード`スキルに`Syukkin`インテントを追加し、Sample Utterancesに`出勤`を設定しました。

他にも適宜退勤用など他のインテントなどを追加したり、もっと高度なやりとりを設定することもできますがめんどくさいので今はSyukkinインテントだけにしておきます。

インテントの設定が済み、`Build Model`をクリックしてモデルをビルドします。

この辺りまで済んだらエンドポイントの設定が必要になります。開発者ポータルは一旦置いておいてサーバの作成に移ります。


# サーバの作成
今回はさくらのクラウド上にUbuntuが動くサーバを作成してWebアプリを動作させます。

[さくらのクラウド コントロールパネル](https://secure.sakura.ad.jp/cloud/)を開いてサーバ作成を行います。

だいたい[この手順](https://qiita.com/sato-w-m/items/eae773e2e4155063f95b)の通りに進めればサーバが出来ます。

設定すべきパラメータは以下の通りです。

* `2.ディスク -> アーカイブ選択` で `Ubuntu 16.04` っぽい項目を選択
* `4.ディスクの修正 -> 管理ユーザのパスワード`になるべく強そうなパスワードを入力（再入力欄も）
* その直下の`ホスト名`に適当に`alexa`とでも入力
* またまた直下の`スタートアップスクリプト`の項目を`shell`に設定、`配置するスタートアップスクリプト`を`[public] apt-get update/upgrade...`にする

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/90c100d7-21fa-1c73-d7cc-af5e29eee00d.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/4e13c444-5227-2ab2-d701-076bf4bac43e.png)

ここまで済んだらあとは何も弄らずに一番下までスクロールして`作成`ボタンをクリックするとサーバが作成されます。

（この時点から課金が発生するのでビタ一金を払いたくない人は事前にそれっぽい人をTwitterで殴る、何らかのイベントに参加するなどしてさくらのクラウドクーポンを入手しておくとよいです）

サーバが作成されて起動してくるまでに多少時間がかかるので適当に5分とか放置しておきます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/9478c30d-0a9d-b6f7-546d-97eaa9deacd4.png)


サーバの起動が終わったらサーバ一覧に戻り、今作成したっぽいサーバを開いてNICタブからIPアドレスを調べます。

あとはSSHでサーバにログインして以下のコマンドを実行します。sudo時にパスワードを訊かれるので適宜サーバ作成時に入力したパスワードを入力してください。

```shell-session
ubuntu@alexa:~$ sudo apt install -y python3-pip
```

ここまで済んだらようやくコードを書きます。

# アプリを作る準備
めんどくさいので今回はPythonでWebアプリを開発しようと思います。`python alexa`等でググると[Flask-Ask](https://github.com/johnwheeler/flask-ask)というライブラリが見つかるのでこれを使います。

flask-askライブラリを使うためにpipでインストールします。

```shell-session
ubuntu@alexa:~$ pip3 install flask-ask
(略)
ubuntu@alexa:~$ python3
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from flask import Flask
>>> from flask_ask import Ask, statement
>>>
```

こんな感じになればOKです。

test.pyなどのファイル名で以下のサンプルコードを保存します。

```py3:test.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
        return 'hello'

if __name__ == '__main__':
        app.run(host='0.0.0.0')
```

保存したら、`python3 test.py`というコマンドを入力します。以下のような表示がされるはずです。

```shell-session
ubuntu@alexa:~$ python3 test.py
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
```

この状態で、Webブラウザに`http://SSHするときに使ったIPアドレス:5000`というURLを入力してみましょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/7c6a90ac-5196-440d-9830-c99949ac76ee.png)

`hello`という表示がされたら上手く動いています。Ctrl+Cで終了させて、最高に面倒くさいTLSの設定に移ります。

# TLS+リバースプロキシの設定
Alexaの呼び出すHTTPエンドポイントはTLSで保護されている必要がありそうなのでLet's Encryptで証明書を取ります。

この手順では既に何らかの独自ドメインを持っていて、使おうと思うホスト名のAレコードがWebアプリを稼働させるサーバに向いていることを想定しています。

ドメインを持っていない場合は[こんな感じ](https://qiita.com/clown0082/items/551d7c081ff6b41b1717)でCNをIPアドレスにしたオレオレ証明書を作るとか[ieserver](http://ieserver.net/)か何かで無料サブドメインを自分のサーバに向けて貰ってLet's Encryptを使うなどの手段が考えられますがめんどくさいので触れません。

独自ドメインを持っていれば楽かと言えばそんなことはないですがやっていきます。[ここ](https://qiita.com/k-yamada-github/items/7314003de7bdcbb2d39b)を元に作業していきます。

以下のようなコマンドを入力していきます。

```
sudo apt install -y letsencrypt nginx
sudo systemctl stop nginx
sudo letsencrypt certonly --standalone -d ドメイン名 (実行するとメールアドレス訊かれるので入力)
sudo nano /etc/nginx/conf.d/alexa.conf
```

/etc/nginx/conf.d/alexa.confの内容は以下のようにします。

```nginx:alexa.conf
server {
    listen 443;
    ssl on;
    server_name ドメイン名;
    ssl_certificate /etc/letsencrypt/live/ドメイン名/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ドメイン名/privkey.pem;


    location / {
        proxy_pass http://127.0.0.1:5000/;
    }
}
```

alexa.confを保存したら`sudo systemctl start nginx`でnginxを起動させます。記述が間違っているなどすれば起動時にエラーが出ます。

ここまで作業を済ませるとTLS経由でリバースプロキシされるようになるはずですが、proxy_pass先が起動していないと`502 Bad Gateway`が出力されます。

アクセスする前に`python3 test.py`を実行し、Runningと出力されたのを確認してからWebブラウザで`https://ドメイン名/`にアクセスします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/fc4fa00a-a40b-184d-e850-10d330299432.png)

`hello`と出力されれば正しく設定されています。

ここまでで雑にHTTPで待ち受けるコードを書けばTLS経由でアクセスできるようになりました。いよいよAlexaのリクエストを受けて適切に応答を返していきます。

# AlexaのHTTPリクエストを受けるためのコード
めんどくさいので動いてるものをなんとなくコピペしてきます。import周りでエラーが出たら適宜pip installしてください。

Slackへの投稿を行うコードが含まれていますが不要な場合は適宜無視してください。

```py3:alexa.py
import requests
import json
from flask import Flask
from flask_ask import Ask, statement
from slackclient import SlackClient

API_TOKEN = "xoxb-なんとかかんとか"

app = Flask(__name__)
ask = Ask(app, '/')
sc = SlackClient(API_TOKEN)

@ask.intent('Syukkin')
def syukkin(firstname):
        speech_text = "ステータスを出勤にしました"
        sc.api_call(
                "chat.postMessage",
                channel="#Slackのチャンネル",
                text="出勤",
                as_user="true",
        )
        return statement(speech_text).simple_card('Syukkin', speech_text)

if __name__ == '__main__':
        app.run()
```

軽くコードの中身というか好きに書き換える際の変えどころをメモしておくと

* インテントと関数の関連付け `@ask.intent('Syukkin')`
* AlexaというかEchoに読み上げさせる文字列 `speech_text = "ステータスを出勤にしました"`
* Alexaアプリで表示されるカード名（たぶん） `return statement(speech_text).simple_card('Syukkin',略`

だいたいこんなところを弄れば自分で作った風になるはずです。もちろん関数内には好きなPythonのコードが記述できるのでSlackに投稿などの好きなことができます。

# エンドポイントの登録
開発者ポータルに戻り、`設定->グローバルフィールド->エンドポイント`の設定を行います。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/caf699e9-1a5f-6f99-f6d6-9d9e38f14b2f.png)

標準では`サービスエンドポイントのタイプ`が`AWS Lambdaの～`になっていますがおすすめは無視してHTTPSに変えます。`デフォルト`の内容も良い感じに自分のURLを入力します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/1dc04db9-b6e2-a5a9-6ba9-f1bad4b4f850.png)

HTTPSではないURLを入力すると意味不明なエラーが出ますがめげずに進めます。

その他のオプションはめんどくさくなさそうな設定（だいたい`いいえ`）にしておきます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/e69cd80b-609b-748f-e318-6c54fbb46f0c.png)

`次へ`をクリックするとSSL証明書の設定へ移ります。Let's Encryptなら良い感じに行けるので一番上の` My development endpoint has a certificate from a trusted certificate authority`にしておきます。

ここまで来たらほとんど設定は終了です。`次へ`をクリックしてテストを有効にします。

よくわからない理由で有効にすることが出来ない場合はモデルの設定が何か間違ってるとかビルドし忘れてるはずです。モデルビルダーに戻って再設定してください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/471607fe-dbfd-4606-e316-071920d945e7.png)

有効化すると、サービスシミュレーターで正常にWebアプリが応答できているか試すことができます。

発話内容を入力してクリックするだけで実機がなくてもデバッグが行えるので非常に便利です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/51b0a4b1-da86-d226-8181-0cb2cefb327e.png)


また、Echoの実機を持っている場合は、テストを有効化した時点で特に追加する操作などなしにAlexaアプリの有効なスキル一覧に開発中のスキルが現れるようになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/b99a6285-c2b2-1589-d48b-f7a2bdb3eaa6.png)

この状態までくれば実際にAlexaに`タイムカードで出勤`などと話しかけることでHTTPSのエンドポイントを叩けるようになっているはずです。

# 動作風景
[![アレ](http://img.youtube.com/vi/KJT5U4WzD-E/0.jpg)](https://youtu.be/KJT5U4WzD-E)
こんな感じです。画像クリックでYouTubeに飛びます。

# 余談1: タイムカードの打刻はどうやってんの

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/8f24739c-c29d-cb3a-ca31-ea553925f754.png)
闇っぽい時間に出勤してますが打刻は行われてないので問題ないです。

既にタイムカードへの打刻機能と仕事してるか通知パトライトへのメッセージング機能を持ったBotが社内LAN直結のクラウド環境で稼働しているのでこれを使います。ちなみにパトライトを光らせるのはsakura.ioを用いて行っています(参考: [去年書いたアドベントカレンダーの記事](http://misodengaku.hatenablog.com/entry/2016/12/15/205934))。

Alexaからのリクエストを受けるのにはインターネットからの疎通があるIPが必要ですが、社内LAN直結の環境でグローバルに口を開けるのはあり得ないのでグローバルに足を出したAlexa用Webアプリから社内Slackにメッセージの投稿を行い、Bot同士でやりとりをさせるような形でタイムカードへの打刻を実現しました。


# 余談2: 合成音声でAlexaに指示を出す方法
デモをする際に自分の声で指示を出してる動画を撮ることほど嫌なこともそうありません。合成音声で指示を出しましょう。

手軽に合成された音声を入手する方法としてまず考えられるのはGoogle翻訳の読み上げ機能です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/4f9b066a-c407-7ea6-6845-89a64826273f.png)

合成音声でAlexaに指示を出す際の基本として、`アレクサ`と指示の間には`。`を挿入して適当にウェイトを入れると良い感じです。だいたい3つぐらいが適当かなと思います。

また、この記事を書いている際に発見したのですが`アレクサ`と発音させるより`アレクサ ア`と発音させると認識率がアップするようです。確かに人間が聞いてもなんとなくこっちの方が自然な気がするので納得がいきます。

で、Google翻訳での指示出しはどうかというと大変微妙です。というのも翻訳内容を読み上げる目的で提供されているサービスなので日常的な会話に比べて大変ゆっくり読み上げられます。その辺りの相性がよくないのかいまいち認識して貰えません。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/6942f453-17a9-0787-15ba-7652b1f7a380.png)

幸いWindowsにナレーターが内蔵されているので適当に呼び出して読み上げさせた([参考](http://ascii.jp/elem/000/000/987/987336/index-2.html))ところ大変具合がよかったです。`アレクサ ア`と併用するとほぼ確実に認識してくれます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/102824/f4b8ccb0-f627-530e-1d15-7a75cd8a3850.png)
ワードアート化すると読み上げられなくなるようです。テキスト情報残ってそうなのに…

# まとめ
雑にやってきましたがクソめんどくさいです。おすすめされない理由がよくわかります。

とはいえ、AWSとか知るかよ的なナウなヤングでもAlexaを使えることがわかったのでOKです。

真面目にやったら正味1時間ぐらいでできたので冬休みの工作におすすめです。