普段からよく使っているAlexaの機能の一つにタイマーがあります。例えばカップラーメンにお湯を入れて「アレクサ、3分のタイマーをセット」みたいな感じですね。これはAlexaの標準機能として提供されているのですが、似たようなことをカスタムスキルでやろうと思うと、
- リマインダーを使う
- 音声ファイルを使う
みたいな感じで擬似的タイマーとして実装するしかなかったと思いますが、これがAPI経由で利用できるようになったみたいです。
ということで早速試してみました。なお、公式のサンプルコードが既に公開されているので(日本語も対応してます)、これをもう少しシンプルにしてステップバイステップな感じでやってみます。全2回の予定です。
タイマーAPIの特徴
- タイマーの操作(作成、削除、現在の状況確認、一時停止、再開)はAPI経由で行います。
- タイマーの利用にはユーザの許可が必要(会話の途中で割り込んでくるため)
- 音声での許可も可能
- タイマー開始後でもセッションが切れない
スキルの作成・対話モデルの作成
とりあえずAlexa-hostedでスキルを新規作成します。Hello Worldスキルのテンプレートを使いましょう。
まず、タイマーはユーザの許可が必要なので、アカウント権限のページでタイマーを有効にします。
次に対話モデルです。まずはタイマーをセットする際のインテント、setTimerIntentを追加して、サンプル発話を登録します。
スロットは、期間を取得するための標準ビルトインスロット、AMAZON.DURATIONを使います。
もう一つ、タイマーがスタートしたあとにタイマーの状態を確認するためのインテント、readTimerIntentも追加しておきます。こちらはスロットは不要です。
まずはここまでです。
コードの準備
ではコードを書いていきましょう、といいたいところですが、まずはpackage.jsonの状態を最新にしておきましょう。
必ずビルド、デプロイしておいてください。
タイマーの権限が許可されているかをチェックする
ということでコードを書いていきます。既にお伝えしたとおり、スキルからタイマーを利用するにはユーザの許可が必要です。LaunchRequestHandlerの中でチェックします。
権限の許可は、handlerInput.requestEnvelope.context.System.user.permissions
をチェックしてconsentTokenを取り出します。これがタイマーAPIを叩くために必要になります。許可が行われていなければトークンが取れませんのでそれで判断できます。トークンが取れてない場合はアレクサアプリに権限許可を促すカードを送信します。タイマーAPIの権限はalexa::alerts:timers:skill:readwrite
になります。
音声での権限許可は次回やりますので、まずは以前からあるカードで。
トークンが取れた場合は権限が許可されていますので、setTimerIntentにユーザを誘導するようアレクサに発話させます。
タイマーのセット
では今日の本題、タイマーのセットです。上で書いたとおり、タイマーのセットはAPI経由で行います。ask-sdkにはAPI経由で利用する機能がいくつかありますが、これらの多くをよしなにやってくれるのがServiceClientFactoryです。
Alexa-hostedのデフォルトのコードだと、スキルオブジェクトの生成はAlexa.SkillBuilders.custom()
で行っているため、ServiceClientFactoryの利用にはApiClientの初期化が必要になります。ちなみにAlexa.SkillBuilders.standard()
の場合は不要です。.withApiClient(new Alexa.DefaultApiClient())
で初期化することで、ServiceClientFactoryが利用可能になります。
では、タイマーをセットするsetTimerIntentのハンドラを実装していきましょう。
最初に権限をチェックしているのは先程と同じです。
次にスロットからユーザが指定した時間を取得します。AMAZON.DURATIONではISO−8601形式で時間を表します。例えば30分ならPT30M
、1時間ならPT1H
みたいな感じです。AMAZON.NUMBERで取ればいいじゃん?と思いましたが、このあとAPIに投げるタイマーオブジェクトがこのフォーマットに従っているためなのですね。
そのユーザが指定した時間を使って、タイマーオブジェクトを生成します。このあたりリマインダーと非常に似てますね。基本的には、duration
で時間を設定、timerLabel
がタイマーの名前になります。で、creationBehavior
はディスプレイ対応デバイスの場合に画面にタイマーを表示するか、triggeringBehavior
でタイマーに対してなにかアクションがあった場合の動作という感じのようです。triggeringBehavior.operation.type
は少し面白そうですが、まずはシンプルに通知するだけのNOTIFY_ONLY
にしておきます。
タイマーオブジェクトの詳細は以下を確認してください。
タイマーオブジェクトができたら、getTimerManagementServiceClient()
でタイマーへのサービスクライアントを初期化、timerServiceClient.createTimerでタイマーオブジェクトをAPIに送信し、タイマーを作成します。APIからの応答でONが返ってきたらタイマーの作成が成功しています。このとき同時にタイマーのIDが返ってきますのでこれをセッションアトリビュートで保存しておきます。これはをタイマーの状況確認を行うreadTimerIntentで使うためです。
タイマーの状態を確認する
次にタイマーの状態を確認するreadTimerIntentを実装していきます。
まず最初にセッションアトリビュートからタイマーのIDを呼び出します。タイマーがセットされていればここで取得できるはずですね。
そして、タイマーサービスクライアント経由で、現在のタイマー一覧を取得します。タイマーは10個まで作成できるため、複数のタイマーが存在する可能性もあります。例えば、タイマーが数時間等になったりしてセッションが終わっている場合とかですかね。それを踏まえて、セッションアトリビュートからタイマーIDが取れれば現在のセッションのタイマー、なければ過去のタイマー一覧の中の1件を取り出し、そのIDでタイマー情報を取得します。
取得したタイマー情報に含まれるtimerStatusを見てタイマーの状態を判断します。
- ON: 起動中
- OFF: オフ状態
- PAUSED: (一時)停止中
今回は取り上げませんが、AudioPlayerのような一時停止や再開もできるのでPAUSEDみたいなステータスがあるようです。もうちょっと詳しい情報が取れ他方がいいかなと思いますが、それはログを取って確認することとします。
あとは、上記の結果を発話して終わりです。
これで必要なインテントハンドラは揃いましたので、リクエストハンドラを追加しておくこともお忘れなく。
テスト
ではテストします。手元で確認してみた限り、タイマーはAlexa開発者コンソールではテストできないようなので実機でテストします。
最初は権限のチェックです。
これまでの権限許可と何ら変わりはありません。カードも同じですね。
権限許可後にタイマーをセットしてみます。
ちょっと最後聞き取りにくいかもしれませんが、スキルが継続しているのにタイマーの時間が来たら関係なくアラームがなっているのがわかるでしょうか?このようにセッションが切れることなく使えるのがタイマーAPIの特徴です。
では最後にもう一つだけ。
複数のタイマーがセットできて、かつ、セッションが終わった後もタイマーが動いているのがわかるでしょうか。また、タイマー停止時に「タイマーからのお知らせです」というセリフも聞こえましたよね。ここがtriggeringBehaviorで設定した箇所です。これをうまく使えば、タイマーの終了時に特定のインテントに誘導するようなことができそうですね。
ちなみにタイマー情報はこんな感じになっていました。
複数の場合は直近のものから順に配列に入っているようです。あと、時間に関する情報がありますが、作成日時・更新日時(作成直後?)・終了予定日時ということで、何分経過したか?みたいなのは自分で算出するしかないですね。画面付きデバイスは一定時間画面に表示されるのでいいんですけど、画面がないデバイスの場合は残り時間がわかったほうが便利かなと思いました(とはいっても、タイマーは進むのである程度の目安にしかならないと思いますが)
ということで、AlexaのタイマーAPIを試してみたという話でした。次は音声による権限の許可と、タイマーの停止・再開をやってみたいと思います。