鍋での炊飯をサポートする Alexa スキルを作った
近ごろ、鍋でお米を炊いています。鍋で炊飯すると、炊飯器で炊飯するよりもおいしく感じられる気がします。
おいしさの印といわれる「かに穴」ができた日はうれしくなります(集合恐怖症ではあるけど)。
ボタンひとつで炊飯できる炊飯器と違い、鍋で炊飯する場合はいくつかのステップが必要です。
私の場合は次のフローで炊飯しています。
- お米を 30 分くらい浸水させる
- 火にかける
- 沸騰したら弱火にして 5 分待つ
- 火をやめて 10 分蒸らす
ここで問題になるのが、弱火にするときと蒸らすときのタイマーセッティングです。浸水時間は適当なのでタイマーはセットしていません。
お米を炊くたびに、タイマーで 5 分、10 分と設定するのは非常に面倒くさいです。
なので、会話の中でタイマーを設定してくれる Alexa スキル「かまどタイマー」を作りました。
この記事では、作成した Alexa スキルの内容と、スキルを実装する上で難しかったことを記載します。
会話フロー
「かまどタイマー」では、次の流れで Alexa と会話します。
Alexa の応答の待ち時間は8秒なので、タイムアウトしてしまう
問題
Alexa からの質問に対してユーザーが答えるのに待ってくれる時間は、8 秒です。 リプロンプトを入れてもさらに 8 秒ユーザーからの応答がなければ、スキルは自動的に終了し、今までのセッション情報は破棄されます。
かまどタイマーの場合、「沸騰したら次へと言ってくれ」と Alexa が言ってきた場合には 、ユーザーは 8 秒以内に「次へ」といわなければなりません。
解決策
Alexa Audio Player を使って音楽を流すと、擬似的にセッションが続いているように見せることができます。
ただし、実際には Alexa Audio Player での音楽再生はスキル外で行われるため、Alexa スキルのセッションは再生開始すると破棄されます。
そのため、セッションにおける「沸騰を待っている」「弱火の 5 分を待っている」といった情報が消えてしまいます。
ここで登場するのが、Audio Player で音楽を流すときに付与できるトークンです。
この Token に「沸騰を待っている」という情報を付与して音楽を再生します。
「アレクサ、次へ」と言ったタイミングでは、再生している Audio Player の Token を取得して照合し、「沸騰待ちであるなら弱火のフローに進む」といった処理を行います。
コード
トークンを付与する方の処理
const YesIntentHandler: RequestHandler = {
canHandle(handlerInput: HandlerInput): boolean {
return (
handlerInput.requestEnvelope.request.type === "IntentRequest" &&
handlerInput.requestEnvelope.request.intent.name === "AMAZON.YesIntent"
);
},
handle(handlerInput: HandlerInput): Response {
const speechText = "沸騰したら「アレクサ、次へ」と言ってください。";
return handlerInput.responseBuilder
.speak(speechText)
.addAudioPlayerPlayDirective("REPLACE_ALL", MP3_URL, "沸騰待ち", 0) // 第3引数がトークン
.getResponse();
},
};
判定側の処理
const NextIntentHandler: RequestHandler = {
canHandle(handlerInput: HandlerInput): boolean {
return (
handlerInput.requestEnvelope.request.type === "IntentRequest" &&
handlerInput.requestEnvelope.request.intent.name === "AMAZON.NextIntent"
);
},
async handle(handlerInput: HandlerInput): Promise<Response> {
if (!handlerInput.requestEnvelope.context.AudioPlayer) {
return handlerInput.responseBuilder
.speak("なんかおかしいです")
.getResponse();
}
if (handlerInput.requestEnvelope.context.AudioPlayer.token === "沸騰待ち") { // ここで判定
// 沸騰
try {
await registerTimer(
handlerInput,
5,
"お知らせです。火を止めてください。火を止めたら「アレクサ、次へ」と言ってください。",
"弱火タイマー"
);
} catch (e) {
console.log(e);
} finally {
const speechText = "弱火タイマー5分をセットしました。";
return handlerInput.responseBuilder
.speak(speechText)
.addAudioPlayerPlayDirective("REPLACE_ALL", MP3_URL, "蒸らし待ち", 0)
.getResponse();
}
}
// ...
};