AlexaでリマインダーAPIを使って、プレゼン用タイマーを作ったお話

# はじめに
みなさん。2018年10月に新機能としてリマインダーAPIが公開されたのをご存知でしょうか？
Alexaにもともとあった機能でリマインダーというものがあったのですが、その機能をスキルから使えるようになりました。

今回、僕はプレゼンテーションを行う時によくある、15分でチーン(発表から質疑に行きましょう）　18分でチーン*2(そろそろですよ） 20分でチーン*3（終わってください）をアレクサに一言いうだけで自動管理してくれた楽だなあと思い、作成してみました。

スマートスピーカーでこの機能を使える便利さを皆さんに伝えたい。そんな思いでこの記事を執筆します。

コードだけ見たい方はgithubに上げています。[コチラ](https://github.com/yutaroud/ReminderAlexa)


## どんなことができるか
[リマインダーAPIリファレンス](https://developer.amazon.com/ja/docs/smapi/alexa-reminders-api-reference.html)
僕が最初にはまった部分なのですが、このリマインダーというのはスキルごとにどうやら管理されるようで、リマインダーの取得だったり削除だったりはスキルから登録したものに対して行われる操作です。
### リマインダーの作成
絶対的な時間の指定と相対的な時間の指定ができます。
絶対的な時間の指定というのは、何月何日何時何分にリマインダーを指定して登録できます。
相対的な時間の指定というのは、いまの時間から何分後にリマインダーという形を指定して登録できます。

公式に載っているリクエストの例を見てみましょう。
・絶対的な時間の指定

```json
{
   "requestTime" : "2016-09-22T19:04:00.672"           // 有効なISO 8601形式 - 実際にイベントが発生する時刻を記述します 
   "trigger": {
        "type" : "SCHEDULED_ABSOLUTE",                 // トリガーのタイプを示します
        "scheduledTime" : "2018-09-22T19:00:00.000"    // 有効なISO 8601形式 - 想定されるトリガー時刻
        "timeZoneId" : "America/Los_Angeles"           // タイムゾーンのIDを含む文字列。 以下の「タイムゾーンの動作」セクションを参照してください。
        "recurrence" : {                               // 反復情報をすべて含める必要があります
            "freq" : "WEEKLY",                         // 反復の頻度のタイプ
            "byDay": ["MO"],                           // 一週間の中での日のリスト
        }
   },
   "alertInfo": {
        "spokenInfo": {
            "content": [{
                "locale": "en-US",                     // 値が指定されるロケール
                "text": "犬の散歩"                // 表示および読み上げ用に使用されるテキスト
            }]
        }
    },
    "pushNotification" : {                            
         "status" : "ENABLED"                          // プッシュ通知を送信するかどうか [default = ENABLED]
    }
}
```

typeの部分でSCHEDULED_ABSOLUTEと記述することで絶対的な時間を指定できます。
localeやtimeZoneIdは基本的に日本で使うことが想定されると思うので、
localeはja-JP
timeZoneIdはAsia/Tokyoにしましょう。
また、freqでリマインドする頻度を設定できます。月、週、曜日ごとで使い分けることができます。
textはリマインドしたい内容を入れます。

・相対的な時間の指定

```json
{
   "requestTime" : "2018-09-22T19:04:00.672"           // 有効なISO 8601形式 - 実際にイベントが発生する時刻を記述します 
   "trigger": {
        "type" : "SCHEDULED_RELATIVE",                 // トリガーのタイプを示します
        "offsetInSeconds" : "7200",                    // リマインダーが相対時刻を使用して設定されている場合、このフィールドを使用してリマインダーが鳴らされるまでの時間を指定します（秒）
   },
   "alertInfo": {
        "spokenInfo": {
            "content": [{
                "locale": "en-US",                     // 値が指定されるロケール
                "text": "犬の散歩"                // 表示および読み上げ用に使用されるテキスト
            }]
        }
    },
    "pushNotification" : {                            
         "status" : "ENABLED"                          // プッシュ通知を送信するかどうか [default = ENABLED]
    }
}
```

typeでSCHEDULED_RELATIVEにすることで相対的な時間の指定ができます。
offsetInSecondsで現在時刻から何秒後にリマインダーをセットするか決められます。
何秒後という部分がキモです。計算しましょう。


### リマインダーの取得
リマインダーにはそれぞれidが割り振られています。
そのidを指定してリマインダーの詳細を取得することができます。
あまり使い方のイメージが想定できないので、誰か使い方のイメージを教えてくださると嬉しいです！！

### 全てのリマインダーの取得
スキル毎に登録されているリマインダーを全て取得してきます。
DBを使ってリマインダーの管理をしない場合は、
リマインダーのidがそもそもシステム的にわからないので、リマインダー更新したいな〜とか削除したいな〜という時に一度リマインダーを取得してからidを引っ張ってきて使うというやり方を僕はしています。
効率的にはどうなんだろうか・・・？

### リマインダーの更新
リマインダーのidを指定して、そのリマインダーを更新できます。
リマインダーの作成に示したような形で、更新したい時間などを指定します。

### リマインダーの削除
リマインダーのidを指定して、そのリマインダーを削除します。


## 実装
### 必要なアカウント
[AWSアカウント](https://aws.amazon.com/jp/)
[Alexaアカウント](https://developer.amazon.com/ja/alexa-skills-kit)

### まず最初に
Alexaスキルを今まで作ったことない人はこちらの記事を参照してください。
[日本発売の三種スマートスピーカーにHelloWorldを喋らせる(Alexa Clova Google Home)](https://qiita.com/yutaroud/items/a68a8772859e3725486a)
AlexaとLambdaを繋いで、スキル開発を行います。連携部分についての説明は上記記事に書いたので
本記事では省いております。ご了承ください。
そして、このリマインダーの機能についてはシミュレータからテストはできないので、iPhoneのAlexaアプリから試すか、実機で試してください。

### Alexa側の設定
画面左にあるアクセス権限にてリマインダーにチェックを入れてください。
そうすることでリマインダーの機能を使うことができます。
![スクリーンショット 2018-12-19 5.47.25.png](https://qiita-image-store.s3.amazonaws.com/0/224825/2506e046-05ce-da2d-5f92-0f38cd7cdb6c.png)
今回は実機でのテストが必要になるので、上部にある公開から必要事項を適当に入力して、実機でテストできるようにしておきましょう！
![スクリーンショット 2018-12-19 6.15.06.png](https://qiita-image-store.s3.amazonaws.com/0/224825/4828a5cc-1040-9711-bfec-c90c89ec93d7.png)
必要事項を入力すると以下のような形でできます。メールでテストもできますが、
https://skills-store.amazon.com/deeplink/~
という招待URLをコピーすることもできます。
このままクリックしてしまうと、海外のサイトに飛ばされてしまうので、comの部分を以下のようなco.jpの形に変更しましょう。
https://skills-store.amazon.co.jp/deeplink/~
![スクリーンショット 2018-12-19 6.16.19.png](https://qiita-image-store.s3.amazonaws.com/0/224825/491f7ce0-17be-0ad4-e40f-81e5c2c9fb17.png)
あとはスキルの許可を実機もしくはスマートフォンで許可してAlexa側の設定は終わりです。

サンプルコードを試してみたい場合は、ビルドタブの画面左側にあるjsonエディターの部分に、今回githubにあげたjsonファイルをドラッグ&ドロップしてください。[コチラ](https://github.com/yutaroud/ReminderAlexa)

### lambda側の設定
lambda側の設定というかコードを書けば終了なのですが、githubにあげた[サンプルコード](https://github.com/yutaroud/ReminderAlexa)について解説します。
今回はAlexa Skill Kitのver2を使用して記述しています。
また、APIなどが楽して書けるため、[ask-utils](https://wp-kyoto.net/launch-ask-utils-to-use-ask-sdk-more-confortable)というライブラリを使用しています。
npmからインストールできます。

```terminal
$npm i -S ask-utils
```



```javascript
canHandle (handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest' &&
      handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent'
  }
```

ask-utilsのライブラリを少しだけ説明しておくと、普通、上のような形でcanHandleに記述しますが、
下記のような形でシンプルに書いたりすることができます。他にも色々ありますが、詳しくは上記のurlからご覧いただくと良いと思います。もしわからないことがあれば、コメント欄で聞いてくださっても大丈夫です！

```javascript
canHandle (handlerInput) {
        return canHandle(handlerInput, 'IntentRequest', 'AMAZON.HelpIntent')
    }
```

#### リマインダーを登録する
今回実装した形だと、10人までリマインダーを登録できるようにしたのですが、リマインダーの登録にかなり時間がかかってしまいます。そのため、今回はLambda側の設定でタイムアウトの時間を増やすことで対応しています。（公式のスキルとして登録しようとすると、8秒以上で自動タイムアウトしないと怒られるので、公式でリマインダーAPIを使うならリクエストは一回か二回が限界かなと思います。）
![スクリーンショット 2018-12-19 6.34.58.png](https://qiita-image-store.s3.amazonaws.com/0/224825/dceeb0fa-0382-c81d-2bfb-86d780dd6f1a.png)

```javascript
const RemindHandler = {
    canHandle (handlerInput) {
        return canHandle(handlerInput, 'IntentRequest', 'RemindIntent')
    },
    async handle (handlerInput){
        const peoplenum = getSlotValueByName(handlerInput, 'num');
        let speechText = peoplenum + '人ですね。研究室のタイマーをセットしました。';
        if(peoplenum >= 10){
            speechText = "10人以上はちょっと多くて覚えられません・・・。ごめんなさい。";
            console.log(peoplenum);
        }else {
            // ここからリクエスト
            const client = new RemidnerClient(handlerInput);
            const limit_time = 20;
            for (let i = 1; i <= peoplenum; i++) {
                client.setPayload(fifteen_timer(i, limit_time));
                await client.fetch('POST');
                client.setPayload(twenty_timer(i, limit_time));
                await  client.fetch('POST');
            }
            // ここまで
        }
        return handlerInput.responseBuilder
            .speak(speechText)
            .getResponse()
    }
};
```

まず、スロットで人数を取得するようにしています。（スロット？ってなった方はAlexa スロットで調べてみてください）その人数の回数だけ、リクエストを繰り返すようにしていて、fifteen_timer関数とtwenty_timer関数でリマインダーの作成のリクエスト文を作成しています。

例として、fifteen_timer関数を参照します。

```javascript
function fifteen_timer(times, limit_time){
    let plus_time = 60*limit_time*(times-1);
    const date = new Date();
    date.setTime(date.getTime() + 1000*60*60*9);
    return {
        requestTime: date.toISOString(),
        trigger: {
            type: "SCHEDULED_RELATIVE",
            offsetInSeconds: 60*(limit_time-5)+plus_time,
        },
        alertInfo: {
            spokenInfo: {
                content: [{
                    locale: "ja-JP",
                    text: times + "人目の発表から"+ (limit_time-5) +"分が経過しましたよ〜そろそろ終わってください!"
                }]
            }
        },
        pushNotification: {
            status: "ENABLED"
        }
    };
}
```

最初のplus_timeは、2人目以降の発表の時間分を計算しています。（15分毎に登録するとだんだん次の人の時間が短くなってしまう）
そしてrequestTimeには現在の時間からという条件を入れるために、Date関数を使用し、現在時刻を算出しています。

あとは見ての通りだと思います。

# おわりに
いかがでしたでしょうか。
みなさん、リマインダーの機能めちゃくちゃ便利です。
ぜひこのリマインダーを使った面白いアイディアを一緒に考えて行きましょう！！！

また、わかりづらいな〜と思ったり、ここ間違ってるな〜と思った方はぜひ一報ください。
即座に修正します！！

最後まで読んでいただきありがとうございました！！