Alexa+lambdaでスマート（じゃない）ホームスキルをつくる

はじめまして@ufoo68です。今回はAWSのアドベントカレンダーにお邪魔させていただきました。

# はじめに
[このアドベントカレンダー](https://qiita.com/advent-calendar/2019/lambda)に登録したきっかけは最近[re:invent2019](http://reinvent.awseventsjapan.com/)に参加した経験からです。普段はあまりAWS関係の記事は書いたりしないのですが、せっかく大きなイベントに参加したのでlambdaについて書いてみよと思った次第です。イベントの発表を聴いて気に入ったサービスについては[この記事](https://qiita.com/ufoo68/items/1012e81255e1ae6b338a)でまとめました。今回はその中のAlexaに挑戦してみようと思い、思い切って[Echo Show 5](https://www.amazon.co.jp/Echo-Show-5-%E3%82%A8%E3%82%B3%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%BC5-%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E4%BB%98%E3%81%8D%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88%E3%82%B9%E3%83%94%E3%83%BC%E3%82%AB%E3%83%BC-with-Alexa-%E3%83%81%E3%83%A3%E3%82%B3%E3%83%BC%E3%83%AB/dp/B07KD87NCM)を購入してみました。

**が、到着予定日がまさかの1/17~1/23**とかいうことになってしまい（サイバーマンデーでポチったのでこういうこになったのか？）、Alexa開発がアドベントカレンダーに間に合わないという事態に！！というわけで、今回はシミュレーターとかを使ってスキルを作ってみようかなと思います。まあ、lambda＋Alexaでの今後の開発のための練習ということにしておきましょうw

# スキルについて

今回つくるのは**「スマート（じゃない）ホームスキル」**です。本当はスマートホーム的なのやりたかったわけですが、何せ実機がないものですからこういうものを考えました。これは何かというと、スマートホームのフリをして返事だけは一丁前にするスキル、つまり「ライトをつけて」とかいうと「わかりました」と返事だけはするが**実際には何もしてくれない**、そんなスマートホームもどきのスキルをつくってみようと思います。

# スキルの実装
今回は[こちら](https://qiita.com/hayao_k/items/d69fb2df6f3e84dfd0ac)を参考にして[CodeStar](https://aws.amazon.com/jp/codestar/)を用いてコードを実装やらデプロイを行いました。これを用いると対話モデルの作成とか全部をAWSでやってくれるみたいです。開発は「Hallo World Skill」というサンプルテンプレートを用いて開発しました。[ここ](https://github.com/ufoo68/alexa-not-smart-home)で公開しています。

まずは、`skill.json`を以下のようにしました。

```javascript:skill.json
{
  "manifest": {
    "publishingInformation": {
      "locales": {
        "ja-JP": {
          "summary": "スマートじゃない、スマートホームスキル",
          "examplePhrases": [
            "Alexa、スマートじゃないホームを起動して",
            "電気をつけて",
            "電気をけして"
          ],
          "name": "スマート（じゃない）ホーム",
          "description": "スマートじゃない、スマートホームスキル"
        }
      },
      "isAvailableWorldwide": true,
      "testingInstructions": "Sample Testing Instructions.",
      "category": "EDUCATION_AND_REFERENCE",
      "distributionCountries": []
    },
    "apis": {
      "custom": {
      }
    },
    "manifestVersion": "1.0"
  }
}

```

あとは対話モデルは以下のように設定しました。

```javascript:interactionModels\custom\ja-JP.json
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "スマートじゃないホーム",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "LightOnIntent",
                    "slots": [],
                    "samples": [
                        "電気をつけて",
                        "ライトをつけて"
                    ]
                },
                {
                    "name": "LightOffIntent",
                    "slots": [],
                    "samples": [
                        "電気を消して",
                        "ライトを消して"
                    ]
                
                }
            ],
            "types": []
        }
    }
}
```

コードもあまりサンプルと変わってませんが、こんな感じです。

```javascript:lambda\custom\index.js
// This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK (v2).
// Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
// session persistence, api calls, and more.
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)
        .getResponse();
    }
};
const LightOnIntentHandler = {
    canHandle(handlerInput) {
      return handlerInput.requestEnvelope.request.type === 'IntentRequest'
        && handlerInput.requestEnvelope.request.intent.name === 'LightOnIntent';
    },
    handle(handlerInput) {
      const speechText = 'はい、つけます。';
      return handlerInput.responseBuilder
        .speak(speechText)
        //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
        .getResponse();
    }
};
const LightOffIntentHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && handlerInput.requestEnvelope.request.intent.name === 'LightOffIntent';
  },
  handle(handlerInput) {
    const speechText = 'はい、消します。';
    return handlerInput.responseBuilder
      .speak(speechText)
      //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
      .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)
        .getResponse();
    }
};
const SessionEndedRequestHandler = {
    canHandle(handlerInput) {
      return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
    },
    handle(handlerInput) {
      // Any cleanup logic goes here.
      return handlerInput.responseBuilder.getResponse();
    }
};

// Generic error handling to capture any syntax or routing errors. If you receive an error
// stating the request handler chain is not found, you have not implemented a handler for
// the intent being invoked or included it in the skill builder below.
const ErrorHandler = {
    canHandle() {
      return true;
    },
    handle(handlerInput, error) {
      console.log(`~~~~ Error handled: ${error.message}`);
      const speechText = `すいません、聞き取れませんでした。`;

      return handlerInput.responseBuilder
        .speak(speechText)
        .reprompt(speechText)
        .getResponse();
    }
};

// This handler acts as the entry point for your skill, routing all request and response
// payloads to the handlers above. Make sure any new handlers or interceptors you've
// defined are included below. The order matters - they're processed top to bottom.
exports.handler = Alexa.SkillBuilders.custom()
  .addRequestHandlers(
    LaunchRequestHandler,
    LightOnIntentHandler,
    LightOffIntentHandler,
    CancelAndStopIntentHandler,
    SessionEndedRequestHandler) // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers
  .addErrorHandlers(
    ErrorHandler)
  .lambda();
```

# 動作のようす

シミュレーターで動かしてこんな感じでちゃんと動いてくれました。

![slill.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/37f1dff0-2e11-6c16-d732-02b012d4b900.png)

まあ、実機もないしスマートホームに対応させてないので返事だけなのですが。。。

# さいごに
とりあえず実機が届いたらもう少し色々やってみたいですね。