デバイス毎に挙動を変えるAlexaスキルの作り方

# 前置き
- Amazon Echo シリーズには様々なデバイスがあり、画面の有無、画面のサイズ感でデバイス毎に挙動を変えたいという時に利用できる方法
- APLが登場して、画面サイズからデバイスの判定が行えるようになった
- 以下の5つを判別
  - 画面なし Echo (Echo, Echo Dot, Echo Plus)
  - Echo Spot (480*480)
  - 旧型 Echo Show (1024*600)
  - 新型 Echo Show (1280*800)
  - FireTV (1920*1080)
- 2018年12月現在の方法なので、今後UIやインターフェースが変更される可能性があります。

# 考え方
- スキルからの JSON 入力の値を解析してデバイスを判別する。
- 画面の無いデバイスは `handlerInput.requestEnvelope.context.Viewport` が `undefined` になる
- 画面のあるデバイスの中から、どのデバイスであるかを判別するには画面の縦横サイズを表す以下の値を確認する
  - `handlerInput.requestEnvelope.context.Viewport.currentPixelWidth`
  - `handlerInput.requestEnvelope.context.Viewport.currentPixelHeight`

# 環境
## ローカル環境
- Node.js 8.10.0
- NPM 5.6.0

## AWS Lambda 環境
- Node.js 8.10

## プロジェクト構成

```
device-check-skill/
   ├ index.js
   ├ package.json
   ├ package-lock.json
   ├ ja-JP.json（モデル）
   └ node_modules/
```

# 実装
## モデル 実装
### スキル作成
- `alexa developer console` にログインする
- スキルを作成する（エンドポイントなどの説明は割愛します）

### インターフェース
- 左メニューの `インターフェース` から以下をオンにする
  - `Alexa Presentation Language`
- `インターフェースを保存` を行う

### JSONエディター
- 呼び出し名を `デバイス確認` にしています。
- カスタムインテントは今回使いませんが、ビルドに必要なので適当なものを入れてます。
- JSONエディターを以下のように編集する

```
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "デバイス確認",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.NavigateHomeIntent",
                    "samples": []
                },
                {
                    "name": "HogeIntent",
                    "slots": [],
                    "samples": [
                        "ほげほげ"
                    ]
                }
            ],
            "types": []
        }
    }
}
```

### ビルド
- `モデルの保存` を行う
- `モデルのビルド` を行う
- 左メニューのカスタムから、4つの必須項目に全てチェックが入ってることを確認する

## Lambda 実装
### ディレクトリ作成
- `> mkdir device-check-skill`
- `> cd device-check-skill`

### package.json 作成
- `> npm init -y`

### package のインストール
- `> npm install --save ask-sdk-core@2.3.0`
- `> npm install --save ask-sdk-model@1.10.1`

### index.js 作成
- `> touch index.js`
- 以下のコードが index.js の中身

```
const Alexa = require('ask-sdk-core');
const SPOT_WIDTH = 480;
const SPOT_HEIGHT = 480;
const OLD_SHOW_WIDTH = 1024;
const OLD_SHOW_HEIGHT = 600;
const NEW_SHOW_WIDTH = 1280;
const NEW_SHOW_HEIGHT = 800;
const FIRE_TV_WIDTH = 1920;
const FIRE_TV_HEIGHT = 1080;

const LaunchNoDisplayRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest'
      && !handlerInput.requestEnvelope.context.Viewport;
  },
  handle(handlerInput) {
    return handlerInput.responseBuilder
      .speak('これは 画面なしの Echo です')  
      .getResponse();
  },
};

const LaunchSpotRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest'
      && handlerInput.requestEnvelope.context.Viewport.currentPixelWidth === SPOT_WIDTH
      && handlerInput.requestEnvelope.context.Viewport.currentPixelHeight === SPOT_HEIGHT;
  },
  handle(handlerInput) {
    return handlerInput.responseBuilder
      .speak('これは Echo Spot です')
      .getResponse();
  },
};

const LaunchOldShowRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest'
      && handlerInput.requestEnvelope.context.Viewport.currentPixelWidth === OLD_SHOW_WIDTH
      && handlerInput.requestEnvelope.context.Viewport.currentPixelHeight === OLD_SHOW_HEIGHT;
  },
  handle(handlerInput) {
    return handlerInput.responseBuilder
      .speak('これは旧型 Echo Show です')
      .getResponse();
  },
};

const LaunchNewShowRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest'
      && handlerInput.requestEnvelope.context.Viewport.currentPixelWidth === NEW_SHOW_WIDTH
      && handlerInput.requestEnvelope.context.Viewport.currentPixelHeight === NEW_SHOW_HEIGHT;
  },
  handle(handlerInput) {
    return handlerInput.responseBuilder
      .speak('これは新型 Echo Show です')
      .getResponse();
  },
};

const LaunchFireTvRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest'
      && handlerInput.requestEnvelope.context.Viewport.currentPixelWidth === FIRE_TV_WIDTH
      && handlerInput.requestEnvelope.context.Viewport.currentPixelHeight ===  FIRE_TV_HEIGHT;
  },
  handle(handlerInput) {
    return handlerInput.responseBuilder
      .speak('これは Fire TV です')
      .getResponse();
  },
};

const skillBuilder = Alexa.SkillBuilders.custom();

exports.handler = skillBuilder
  .addRequestHandlers(
    LaunchNoDisplayRequestHandler,
    LaunchSpotRequestHandler,
    LaunchOldShowRequestHandler, 
    LaunchNewShowRequestHandler,
    LaunchFireTvRequestHandler
  )
  .lambda();
```

### AWS Lambda 関数にアップロード
- AWS Lambda 関数に以下のファイルをZIPに圧縮してアップロードする
  - index.js
  - package.json
  - package-lock.json
  - node_modules/
- 保存する


# 確認方法
## ブラウザで確認
- `alexa developer console` にログインする
- 上タブから `テスト` を選択
- `スキルテストが有効になっているステージ:` を `開発中` に変更
- Alexaシミュレーターで `呼び出し名` を入力するか、マイクから発話して挙動を確認する
- `Device Display` にチェックを入れて下にスクロールすると、試したいデバイスの選択が可能
- 画面なしデバイスの確認はできない

## 実機で確認
- Amazon Echo デバイスに `alexa developer console` で利用した Amazon アカウントを設定する
- 「Alexa `呼び出し名`」で呼び出してみる