こんにちは Clom です。
最近 GitHub がプライベートリポジトリ無料化しましたね。

今までプライベートリポジトリにいたコードはフリープランに変更すると確認できなくなっていましたが
今回の変更により、確認できるようになりましたので、コードを見返しながら、ふとAlexa の話を思い出しました。
昔Alexa と Line のBeacon を使って在室管理ツールを作っていた話をコードを引っ張ってきて話をしようかと思います。

https://speakerdeck.com/clom/bu-wu-falsezai-shi-woguan-li-suru

当時、勉強会の中で LTしていたのでその時の資料です。(今となっては雑に作ったなと思ってました。)


{F297D7E2-A046-4AE0-A234-887E6198CF54}.png
はじめに

この時のモチベーションとしてはスマートスピーカー(Google Home, Echo dot)を何かできないかなと買っていたときにLineBeacon と組み合わせたら面白いことができるのでは?
と思い立って始めたのが在室管理ツールです。

在室管理ツールとは
LineBeacon の近くにいるとLine のユーザー名を登録し、スマートスピーカーに対してだれがいるかを問い合わせすることでだれがいるかを知らせてくれるものとして作っていました。

実装にあたって
このツールを作っていた時に使ったものとしては以下のものになります。
Alexa 側
・Node
・EchoDot
・Lambda

Line側
・PHP(Lumen)
・LineBeacon

共通して使ったもの
・DynamoDB (プライマリーキーに userId を使用してユーザーのIDと名前を格納)

構成

{2FD43574-838E-44D0-9C1F-D573952CF33E}.png

当時はこのような構成をしていましたが、 AWS内のみで完結するとすれば Lambda+API Gateway の組み合わせでだいぶスッキリとした構成になったかなと思っています。

ちなみにAlexa の場合、スキルという形でサービスを登録するのですが、エンドポイントがLambda の ARN か HTTPS のURLを指定することができます。
この時の構成として、Let's Encrypt するのもちょっと面倒だったという背景もありAlexaとの通信部分は Lambda を利用したという経緯もあります。
(Line bot もエンドポイント指定は https 必須なので後々考えると一つにまとめれたのではとは思っています。)

仕組み
このツールの仕組みとして
・Beacon 周辺に人が来た場合
Beacon を受け取った端末から Beaconに連携しているLineBOTのエンドポイントに対してリクエストを送ります。(連携しているLine BOT とフレンドになっている必要があります)

{  
   "replyToken":"nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
   "type":"beacon",
   "timestamp":1462629479859,
   "source":{  
      "type":"user",
      "userId":"U4af4980629..."
   },
   "beacon":{  
      "hwid":"d41d8cd98f",
      "type":"enter"
   }
}

送られるBeacon イベントはこのような形で送られ、 beacon.type が enter の場合は beacon 周辺にいることを示し、 leave の場合は離れたことを示します。
これによって、入室イベントと退出イベントの両方を取得することが可能になります。

enter のbeaconイベントを受け取るとユーザーの情報からユーザー名を取得し、userId と共に DynamoDB にPutItem で格納します。
そのあとLine に対して"入室した"というメッセージを通知するようにしています。

・Beacon から離れた場合
leave のbeaconイベントを受け取ると"退出した"というメッセージをBeaconを受け取った端末にたいして通知を行います。
DynamoDB には userId を用いて該当のデータを取得し deleteItem で削除を行っています。

ここまでがLineBOT側の実装になります。
https://github.com/clom/Alexa-Line/blob/master/app/Http/Controllers/Api/CallbackController.php

次にAlexa 側の実装になります。
Alexa 側のエンドポイント実装にはスキルを作成する必要があります。
スキルを登録することで実際にEchoに対して話しかけることで対応したアクションを行うことが可能になります。

{D80FDC6C-2394-4693-9702-2DF9782D540F}.png

スキルを利用するためには呼び出し名を指定する必要があります。
"〇〇を開いて!"といった形で特定のスキルを呼び出す際に使用します。

次にイベントのハンドリングとしてインテントがあります。
インテントにはAmazon 側で定義されている3つに加えて、
アクションを起こすものに対してインテントを作成します。

AMAZON.CancelIntent - キャンセル時に起きる
AMAZON.HelpIntent - ヘルプした際に起きる
AMAZON.StopIntent - 停止・終了時に起きる
(この3つはビルドインインテントという扱いになります。)

今回の場合は在室管理を問い合わせるのみなので追加で一つを行いました。
各インテントにはこのケースに入るための発話を登録する必要があります。
コメント 2019-01-24 015721
例えば、今回作成した在室管理を行うインテントでは、
・誰がいる
この部屋に誰がいる
などといったワードを登録してこのワードをAlexaが認識するとこのケースを実行するという形になります。

実装としては以下のコードで実現は可能にはなりますが、ハンドラを定義してそれぞれに対応した処理を書く形になります。

    const alexa = Alexa.handler(event, context);
    alexa.APP_ID = APP_ID;
    // To enable string internationalization (i18n) features, set a resources object.
    alexa.registerHandlers(handlers);
    alexa.execute();

ハンドラではコンソールで定義したインテントのほかにスキル実行時に行われる LaunchRequest も必要になります。
emit を行うことにより、Alexa 側に話すワードを送ることが可能になります。
引数に指定している ask, tell ですが、 ask の場合はAlexa がワードを読み上げた後にもう一度問い合わせる状態になり、そこから次へのアクションに繋げることが可能になります。
一方 tell の場合はワードを読み上げた後はスキルが終了します。そのため、再度実行する場合はスキルを呼び出す必要があります。
const handlers = {
    'LaunchRequest': function () {
        this.emit(':ask', 'ようこそ!このスキルでは今の部屋の在室を確認できます。確認するには、誰がいる。と話しかけて見てください。');
    },
    'NowOccupy': function () {
       readDynamoItem(params, users => {
            var user = '';
            user = users;
            
            if(user.length != 0)
                this.emit(':tell', 'ただいま'+ user +'が部屋に居ます。');
            else
                this.emit(':tell', 'だれも部屋にはいません');
                
        });
    },
    'AMAZON.HelpIntent': function () {
        this.emit(':ask', '誰がいる?っと話してみてください');
    },
    'AMAZON.CancelIntent': function () {
        this.emit(':tell', 'キャンセルします');
    },
    'AMAZON.StopIntent': function () {
        this.emit(':tell', '終了します');
    },
};

ユーザーの取得については DynamoDB のテーブルに登録されているレコード全員がいま居ることになっていますので scan によりデータ取得します。
取得したユーザーの名前を一つの文字列にして渡すことにより、Alexaから〇〇がいますというワードを喋らせることができます。

ちなみにAlexa側のlambdaはこんな感じで実装していました。



このようにして LineBeaconと Alexaを利用して在室管理を行えるようになりました。
応用すると端末を持ち込むだけで出勤・退勤時間を計測できるようになるのでは?とは思っています。
時間の管理をしてみたい方にはぜひ試してみてください。