Amazon Echo Dotがやっと家に来ました。
ので、こいつでも何か自作のアプリを作ってみようと思い、前の記事
amamako.hateblo.jp
で作ったものと同じような機能を持つアプリの、
Amazon Alexa版を作ってみることにします。
結果がこちらです。
Amazon Echoに今NHKで放送している番組を教えてもらう
作り方
下記のページの「スキル開発を始める」をクリックし、Amazonのアカウント情報を入力して開発を始めます。
developer.amazon.com
Alexa Skill Kitの「始める」をクリック。
「新しいスキルを開発」をクリック。
「カスタム対話モデル」を選択し、言語・スキル名・呼び出し名などを入力していき、画面下部の「次へ」をクリックします。
そうするとこの画面になります。
ここで前回の記事でも行ったように、IntentやSlots(前回の記事で言うEntities)の登録を行っていきます。 Intentとは要するに「こういう文がきたらこういう機能を呼び出すよ」という文例の定義で、Slotsは「文中にこういう単語が入りうるよ」という単語集の定義です。詳しくは公式のドキュメント
developer.amazon.com
や解説記事
dev.classmethod.jp
などをご参照ください。
ただ、実はこの画面からは簡易的な定義しかできません。ちょっと複雑な定義、例えば同じ単語に2つ以上呼び名がある場合などのSlotsの定義はここからはできないので、画面の「スキルビルダーを起動する」からスキルビルダーを起動します。
そうするとこんな画面が出てきます。ここでCode Editorを選択し
以下のコードを入力。「Apply Changes」、「Save Model」、「Build Model」とクリックしていきます。
{
"languageModel": {
"types": [
{
"name": "LIST_OF_STATIONS",
"values": [
{
"id": "sougou",
"name": {
"value": "NHK総合テレビ",
"synonyms": [
"総合テレビ",
"NHK",
"総合",
"nhk 総合テレビ",
"nhk"
]
}
},
{
"id": "etv",
"name": {
"value": "NHK教育テレビ",
"synonyms": [
"教育テレビ",
"教育",
"nhk 教育テレビ"
]
}
},
{
"id": "bs1",
"name": {
"value": "NHKBS1",
"synonyms": [
"BS1",
"nhkbs1"
]
}
},
{
"id": "bspremium",
"name": {
"value": "NHKBSプレミアム",
"synonyms": [
"BSプレミアム",
"nhk bsプレミアム",
"bsプレミアム"
]
}
}
]
}
],
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "GetTVProgram",
"samples": [
"{Station} で放送している番組を教えて",
"{Station}"
],
"slots": [
{
"name": "Station",
"type": "LIST_OF_STATIONS"
}
]
}
],
"invocationName": "ほたるの番組表"
}
}
それぞれの定義の意味、特にidやsynonymsについては、下記のドキュメントを見てください。
developer.amazon.com
次に、画面の「Configuration」をクリックします。するとこんな画面が出てきます。
前回の記事ではFirebaseを用いてバックグラウンドの処理を実装したわけですが、今回はAmazonのサービスなのでAWS Lambdaを用います。
というわけでAWSのコンソールに入り、Lambdaを選択。
「新しい関数を作成」を選択。
「設計図」を選択し、下の検索欄に「Alexa」と入力するとAlexaのテンプレートが出てくるので、「alexa-skills-kit-color-expert」を選択し、「設定」をクリックします。
すると以下のような画面が出てくるので、名前を入力し、ロール(アプリが他のAWSのリソースにアクセスする権限をまとめたもの)としてテンプレートから新しいロールを作成、ページ下部にある「関数を作成」をクリックします。
そうすると以下のような画面が出てきます。
コード編集欄でコードを編集していきます。今回のアプリではテンプレートをもとに以下のようなコードにしました。
'use strict';
const http = require('http');
const AREA = '';//取得したい番組表の地域ID
const API_KEY = '';//ここにNHK番組表のAPI_KEYを入れておきます。
/**
* This sample demonstrates a simple skill built with the Amazon Alexa Skills Kit.
* The Intent Schema, Custom Slots, and Sample Utterances for this skill, as well as
* testing instructions are located at http://amzn.to/1LzFrj6
*
* For additional samples, visit the Alexa Skills Kit Getting Started guide at
* http://amzn.to/1LGWsLG
*/
// --------------- Helpers that build all of the responses -----------------------
function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: 'PlainText',
text: output,
},
card: {
type: 'Simple',
title: title,
content: output,
},
reprompt: {
outputSpeech: {
type: 'PlainText',
text: repromptText,
},
},
shouldEndSession,
};
}
function buildResponse(sessionAttributes, speechletResponse) {
return {
version: '1.0',
sessionAttributes,
response: speechletResponse,
};
}
// --------------- Functions that control the skill's behavior -----------------------
function getWelcomeResponse(callback) {
// If we wanted to initialize the session to have some attributes we could add those here.
const sessionAttributes = {};
const cardTitle = 'こんにちわ';
const speechOutput = 'こんにちわ、ほたるの番組表です。 ' +
'いま放送している番組を知りたい、テレビ局を教えて。';
// If the user either does not reply to the welcome message or says something that is not
// understood, they will be prompted again with this text.
const repromptText = 'いま放送している番組を知りたい、テレビ局を教えて。';
const shouldEndSession = false;
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}
function handleSessionEndRequest(callback) {
const cardTitle = 'Session Ended';
const speechOutput = 'さようなら。';
// Setting this to true ends the session and exits the skill.
const shouldEndSession = true;
callback({}, buildSpeechletResponse(cardTitle, speechOutput, null, shouldEndSession));
}
/**
* Sets the color in the session and prepares the speech to reply to the user.
*/
function getTVProgram(intent, callback) {
const cardTitle = "いま放送している番組";
const stationNameSlot = intent.slots.Station;
let repromptText = '';
let sessionAttributes = {};
let shouldEndSession = true;
let speechOutput = '';
const tvStation = {
"sougou": "g1",
"etv": "e1",
"bs1": "s1",
"bspremium": "s3"
};
console.log("called Station Name");
console.log(stationNameSlot.value);
let tvStationId = undefined;
let tvStationName = undefined;
if(stationNameSlot.resolutions
&& stationNameSlot.resolutions.resolutionsPerAuthority[0].values
&& tvStation[stationNameSlot.resolutions.resolutionsPerAuthority[0].values[0].value.id] ){
tvStationId = tvStation[stationNameSlot.resolutions.resolutionsPerAuthority[0].values[0].value.id];
tvStationName = stationNameSlot.resolutions.resolutionsPerAuthority[0].values[0].value.name;
}
if (tvStationId) {
shouldEndSession = true;
//オプションを定義
var url = 'http://api.nhk.or.jp/v2/pg/now/' + AREA + '/' + tvStationId + '.json?key=' + API_KEY;
http.get(url, function(res) {
var body = '';
res.setEncoding('utf8');
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
var ret = JSON.parse(body);
speechOutput = `いま ${tvStationName} で放送している番組は` + ret.nowonair_list[tvStationId].present.title + 'です'; // Send simple response to user\
repromptText = speechOutput;
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
});
}).on('error', function(e) {
console.log(e.message);
});
} else {
shouldEndSession = false;
speechOutput = "すみません、よくわかりません。";
repromptText = "すみません、よくわかりません。";
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}
}
// --------------- Events -----------------------
/**
* Called when the user launches the skill without specifying what they want.
*/
function onLaunch(launchRequest, callback) {
console.log(`onLaunch requestId=${launchRequest.requestId}`);
// Dispatch to your skill's launch.
getWelcomeResponse(callback);
}
/**
* Called when the user specifies an intent for this skill.
*/
function onIntent(intentRequest,callback) {
console.log(`onIntent requestId=${intentRequest.requestId}`);
const intent = intentRequest.intent;
const intentName = intentRequest.intent.name;
// Dispatch to your skill's intent handlers
if (intentName === 'GetTVProgram') {
getTVProgram(intent, callback);
} else if (intentName === 'AMAZON.HelpIntent') {
getWelcomeResponse(callback);
} else if (intentName === 'AMAZON.StopIntent' || intentName === 'AMAZON.CancelIntent') {
handleSessionEndRequest(callback);
} else {
throw new Error('Invalid intent');
}
}
// --------------- Main handler -----------------------
// Route the incoming request based on type (LaunchRequest, IntentRequest,
// etc.) The JSON body of the request is provided in the event parameter.
exports.handler = (event, context, callback) => {
try {
if (event.request.type === 'LaunchRequest') {
onLaunch(event.request,
(sessionAttributes, speechletResponse) => {
callback(null, buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === 'IntentRequest') {
onIntent(event.request,
(sessionAttributes, speechletResponse) => {
callback(null, buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === 'SessionEndedRequest') {
callback();
}
} catch (err) {
callback(err);
}
};
コードを入力後、関数を保存、ARNをコピーし、Configurationの画面に戻り入力、「次へ」を入力すると、自身のAmazon Echoでアプリを動かすことが可能になります。
感想
スマートスピーカーのアプリの作り方は、Google AssistantもAmazon Alexaも似たようなものみたいですね。