Hello World. ゼロから始める Alexa 開発

前回までは ITFFF を用いてちょっとした通知アプリを作ってみました
しかし、本命は Alexa Skills Kit を用いてアプリケーションを作成したいと思ってる！

今回は、 Hello World. ゼロから始める Alexa 開発 と題して始めてみたいと思ってます

# 環境

* Amazon Echo Dot (通称：あれくさ)
* AWS Lambda
* Visual Studio Code
* node.js
* Alexa Skills kit（開発者コンソール）

# まずはじめに

やさしくはじめるスマートスピーカープログラミングを読んで実装するつもりだった。
しかし…うまく動かない:sweat:
であればもっと初心者向けに色々あるんじゃないかと調べて、サンプルを作るにあたって学習した内容からまとめてみました

# 学習編

* [Alexa 道場](https://developer.amazon.com/ja-JP/alexa/alexa-skills-kit/webinars#season2)

You Tube を用いて仕組みを説明しながらサンプルプログラミングの作り方を説明してくれる
この構築の場合は、AWS Lambda がなくても実装できるのでお手軽で仕組みの理解もしやすかった

* [ASK SDK for Node.js](https://ask-sdk-for-nodejs.readthedocs.io/ja/latest/Developing-Your-First-Skill.html#)

Alexa 道場で学習中に軽く見たけれどコードの書き方などがまとまっている
多分本格的に実装するときには参考にすることが増えそう

## 得た知識

Alexa Skill Kit でアプリケーションを実行するには以下のような呼び出し方をする

パターンA

```
声：へろーわーるどを開いてぬるぽして
あれくさ：ヽ( ･∀･)ﾉ┌┛ｶﾞｯΣ(ﾉ`Д´)ﾉ（実際はガッって言うだけ）
```

パターンB

```
声：へろーわーるどを開いて
あれくさ：「ぬるぽ」と言ってください
声：ぬるぽ
あれくさ：ヽ( ･∀･)ﾉ┌┛ｶﾞｯΣ(ﾉ`Д´)ﾉ（実際はガッって言うだけ）
```

1行で言い切るか、対話形式でやり取りが出来る
やり取りできるものを スキル というらしい
（スマホ（Alexa）のアプリ（スキル）と意識するとわかりやすいかも？）

### Alexa の Request の種類について

#### LaunchRequest

「ｘｘｘを開いて」をトリガーとしてスキルを実行する
実行したときの最初に呼び出されるリクエスト種別

#### IntentRequest

スキルを起動後に呼び出されるリクエスト種別
LaunchRequest の後に「ｙｙｙして」などを付けると呼び出される
「ｘｘｘを開いてｙｙｙして」とした時は LaunchRequest を飛ばして、IntentRequest が呼ばれる
リクエストと組み合わせて intent（インテント） が実行される
intent はアクションという認識で、自作で作るものから、ヘルプ、キャンセルという Alexa Skills Kit にデフォルトで用意されているものが存在する

#### SessionEndedRequest

応答がなかった場合等に呼び出される
例）ｘｘｘを開いて （この後放置する）等

# 実装編

## 処理のイメージ

1. Amazon Echo Dot（命令）
2. Alexa スキル(声を受け取る)
3. AWS Lambda(声を処理する)
4. Amazon Echo Dot（返答）

## 作るもの

```
声：あれくさ、応答テスト
あれくさ：「ぬるぽ」と言ってください
声：ぬるぽ
あれくさ：ヽ( ･∀･)ﾉ┌┛ｶﾞｯΣ(ﾉ`Д´)ﾉ（実際はガッって言うだけ）
```
デフォルトの無料枠内のものではなく AWS Lambda を用いる

## スキルを作成する

Alexa Skills kit 開発者コンソール上で「スキルの作成」を押下する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532289/858513f2-58bc-a1a4-aac9-fced5d0f5692.png)

スキル名は単語2つを組み合わせるような形で設定する必要があるので「応答テスト」とする
設定については以下の画像の選択を行うものとする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532289/2cba07be-20c1-3c4e-d12b-10ecf1e07fca.png)

## インテントを作成する

スキルを作成したら、声の受け口インテントを作成する
ビルトインテントは Alexa が用意しているアクションなので今回はスルーする
このサンプル発音に該当する発音が行われた場合 HelloIntent が呼び出されるようになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532289/90d936fe-f332-c3b8-4841-843c69877869.png)

## AWS Lambda の側を作成しスキルと連携する

連携するためにスキル ID をコピーしておく

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532289/b16bb47a-8ee3-28d8-ba5e-5e3aa38f0456.png)

AWS コンソールからLambdaを選択して関数を作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532289/052548bb-ba68-b0f4-7d3a-a57933f46c34.png)

完成作成後、トリガーに Alexa Skills Kit を選択する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532289/58f66cac-9e95-13b3-3eb7-68478929b827.png)

Lambda の右上にARNがあるのでこれをコピーしエンドポイントのデフォルトの地域に張り付けて保存しておく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532289/ed7181a5-7406-2067-c147-8e7134cb623d.png)

ここまで出来るとスキルと Lambda の連携は完了しているはず！

## AWS Lambda の実装を行う

Visual Studio Code を開く
空のディレクトリに移動して以下の呪文を唱えていく

```shell:
npm init
npm install --save ask-sdk-model
npm install --save ask-sdk-core
```

以下のようにしたいけど容量が大きくてzipファイルアップロードをすると
AWS コンソール上でソースの編集が出来なくなるので必要なものだけ取り込む

```shell:
npm init
npm install --save ask-sdk
``` 

直下に index.js を作成して以下のソースコードを張り付ける

```javascript:
/* eslint-disable  func-names */
/* eslint-disable  no-console */

const Alexa = require('ask-sdk-core');

// 起動時に処理するハンドラ
const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  handle(handlerInput) {
    const speechText = 'ようこそ。「ぬるぽ」と言ってください。';

    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(speechText)
      .withSimpleCard('こんにちは。', speechText)
      .getResponse();
  },
};

// スキル起動後に実行される intent.name で処理を分岐している
const HelloIntentHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && handlerInput.requestEnvelope.request.intent.name === 'HelloIntent';
  },
  handle(handlerInput) {
    const speechText = 'ガッ';

    return handlerInput.responseBuilder
      .speak(speechText)
      .withSimpleCard('5ch', 'ヽ( ･∀･)ﾉ┌┛ｶﾞｯΣ(ﾉ`Д´)ﾉ')
      .getResponse();
  },
};

// デフォルトで用意されているインテントでスキル起動後「ヘルプ」というと実行される
const HelpIntentHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent';
  },
  handle(handlerInput) {
    const speechText = '「ぬるぽ」と話しかけてください。';

    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(speechText)
      .withSimpleCard('5ch', speechText)
      .getResponse();
  },
};

// デフォルトで用意されているインテントでスキル起動後「キャンセル」「ストップ」というと実行される
const CancelAndStopIntentHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent'
        || handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent');
  },
  handle(handlerInput) {
    const speechText = 'さようなら。';

    return handlerInput.responseBuilder
      .speak(speechText)
      .withSimpleCard('こんにちは。', speechText)
      .getResponse();
  },
};

const SessionEndedRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
  },
  handle(handlerInput) {
    console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);

    return handlerInput.responseBuilder.getResponse();
  },
};

// 応答できなかった場合等のエラー対応
const ErrorHandler = {
  canHandle() {
    return true;
  },
  handle(handlerInput, error) {
    console.log(`Error handled: ${error.message}`);

    return handlerInput.responseBuilder
      .speak('すみません。理解できませんでした。もう一度お願いします。')
      .reprompt('すみません。理解できませんでした。もう一度お願いします。')
      .getResponse();
  },
};

// ハンドラを作成した後は以下で登録する必要がある
const skillBuilder = Alexa.SkillBuilders.custom();

exports.handler = skillBuilder
  .addRequestHandlers(
    LaunchRequestHandler,
    HelloIntentHandler,
    HelpIntentHandler,
    CancelAndStopIntentHandler,
    SessionEndedRequestHandler
  )
  .addErrorHandlers(ErrorHandler)
  .lambda();
```

## 動作させてみる

すべて緑色のチェックがついていることを確認する
チェックされていなければされていない部分をクリックする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532289/cb2b966b-786a-c658-57f0-7e434007aecc.png)

ソースのビルドを行う
個人的にはモデル保存時にビルドするのが良いと思っている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532289/967ed825-130f-2572-fd7e-83ed7f2a74b7.png)

スキルテストが有効になっているステージを「開発中」に変更する
そのあと、下の入力またはマイク長押しで「応答テスト」と言ってみる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532289/f70bf321-c4a0-bc4f-d893-b784fae063fd.png)
。。。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532289/8922b52e-fccf-4573-9b3a-58037ba0bbf8.png)
うまくいったね！
ここまで来たら自分のあれくさに言ってみる
「あれくさ、応答テスト」
アレクサと楽しい会話ライフが始まるかもしれません
今回のサンプルは実際動かしてみると、抑揚のない「ガッ」に失笑間違いなし！
（ところどころ単語に誤字があったりしますがご了承ください）

# 最後に

withSimpleCard を用いるとディスプレイに文字が出るらしいが、テストだと表示されなかった・・・
うまく動くかわからないので、ディスプレイ型のスマートスピーカーも気になる:persevere: