はじめまして@ufoo68です。今回はAWSのアドベントカレンダーにお邪魔させていただきました。
はじめに
このアドベントカレンダーに登録したきっかけは最近re:invent2019に参加した経験からです。普段はあまりAWS関係の記事は書いたりしないのですが、せっかく大きなイベントに参加したのでlambdaについて書いてみよと思った次第です。イベントの発表を聴いて気に入ったサービスについてはこの記事でまとめました。今回はその中のAlexaに挑戦してみようと思い、思い切ってEcho Show 5を購入してみました。
が、到着予定日がまさかの1/17~1/23とかいうことになってしまい(サイバーマンデーでポチったのでこういうこになったのか?)、Alexa開発がアドベントカレンダーに間に合わないという事態に!!というわけで、今回はシミュレーターとかを使ってスキルを作ってみようかなと思います。まあ、lambda+Alexaでの今後の開発のための練習ということにしておきましょうw
スキルについて
今回つくるのは「スマート(じゃない)ホームスキル」です。本当はスマートホーム的なのやりたかったわけですが、何せ実機がないものですからこういうものを考えました。これは何かというと、スマートホームのフリをして返事だけは一丁前にするスキル、つまり「ライトをつけて」とかいうと「わかりました」と返事だけはするが実際には何もしてくれない、そんなスマートホームもどきのスキルをつくってみようと思います。
スキルの実装
今回はこちらを参考にしてCodeStarを用いてコードを実装やらデプロイを行いました。これを用いると対話モデルの作成とか全部をAWSでやってくれるみたいです。開発は「Hallo World Skill」というサンプルテンプレートを用いて開発しました。ここで公開しています。
まずは、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"
}
}
あとは対話モデルは以下のように設定しました。
{
"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": []
}
}
}
コードもあまりサンプルと変わってませんが、こんな感じです。
// 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();
動作のようす
シミュレーターで動かしてこんな感じでちゃんと動いてくれました。
まあ、実機もないしスマートホームに対応させてないので返事だけなのですが。。。
さいごに
とりあえず実機が届いたらもう少し色々やってみたいですね。