BeacappのAPI機能を使って、ログの集計画面を作ってみる


#はじめに

Beacappには、Beacon管理や、SDK以外にもログのAPI機能があります。
APIの何が良いかというと、

* 人の移動
* 密集具合
* 滞在時間
* Beaconの電波状況
* アプリの使用頻度


カスタムログを使えば、、、

* ユーザの受けが良いクーポン
* アプリの画面遷移でも

などなど、これまであまり知ることの出来なかった情報や、実装にはちょっと手間のかかる情報を、知ることができます。
また、既存のシステムなどからも呼び出して連携させることもできます。

今回は、この機能を使って、Beaconを最後に検知した時間を一覧で閲覧する画面を作ってみます。

Beacappについては、[こちら](http://qiita.com/JMASystems/items/b57c2689d22a42cbe36e)を参照してください。
閲覧画面については、Bluemix(Node.js)を使ってみたいと思います。

#環境準備
今回の開発環境は以下の通りです。

## 使用するサービス
* [Beacapp](http://www.beacapp.com)
* [Bluemix](https://www.ibm.com/cloud-computing/jp/ja/bluemix/)

##開発環境

* Mac OSX
* [Web Storm](https://www.jetbrains.com/webstorm/)


#Beacappの準備
##アカウント作成からアプリの登録まで

[事前準備](http://qiita.com/JMASystems/items/b57c2689d22a42cbe36e)を参照してください。

## APIキーの発行

BeacappのAPIへアクセスするためには、アカウントごとに、APIキーが必要です。
APIキーの発行は、現在(2016年8月)はjena,JMASで実施していますので、お知らせください。(beacapp_support@jmas.co.jp)

#集計画面を作る
##Bluemixの準備

BluemixはIBM社が提供しているPaaSで、Cloud Foundryや、OpenStack,Dockerの3つの技術をベースとしています。
詳細な説明は別の機会に譲りますが、今回はこの中から、Cloud Foundryで提供されている、Node.jsのRuntimeをつかいます。

##アカウント作成

以下のサイトから、１ヶ月間無料で使えるアカウントが作成できます。
[Bluemix](https://www.ibm.com/cloud-computing/jp/ja/bluemix/)

##Node.js環境を用意する
ログインしたら、カタログから「Cloud Foundry アプリケーション」を選択します。
![スクリーンショット 2016-08-19 午後1.36.47.png](https://qiita-image-store.s3.amazonaws.com/0/130138/f0e4d3fd-3e36-31e1-446e-e887c5210e77.png "スクリーンショット 2016-08-19 午後1.36.47.png")
 
遷移したら「SDK for Node.js」を選択します。
あとは、アプリケーション名を入力して、作成すれば準備OKです。

##Macの開発環境を整える

アプリケーションを作成すると
「どのようにコーディングを開始しますか?」
という画面に遷移します。

基本的にはそこに記載された通りの手順を実行すれば良いです。
※とても親切な設計！
だいたい以下のようなことが書いてあります。

1. Cloud Foundry および IBM® Bluemix® コマンド・ライン・インター フェースをインストールします。
[CF CLI](https://github.com/cloudfoundry/cli/releases)
[Bluemix CLI](http://clis.ng.bluemix.net/ui/home.html)

1. スターター・コードをダウンロードします。
 (これは Hello World的なものです。すでに、プロジェクトがあれば不要です。)

1. ダウンロードしたソースコードを解凍します。

1. コマンドラインを起動します。

1. 解凍したディレクトリ（プロジェクトフォルダ）に遷移します。

    ```
    $ cd YOUR_NEW_DIRECTORY
    ```

1. コマンドラインからBluemixに接続します。

    ```
    $ bluemix api https://api.ng.bluemix.net
    ```

1. Bluemixにログインします。

    ```
    $ bluemix login -u YOUR_BLUEMIX_ID -o YOUR_BLUEMIX_ID -s YOUR_BLUEMIX_SPACE
    ```
 
1. アプリをBluemixにデプロイします。

    ```
    $ cf push YOUR_APP_NAME
    ```
 
1. ブラウザでアクセスしてみる。
アクセス先のURLは、WebのBluemixコンソールなどから確認できます。
  
とりあえず、これでHello World的なところまで完了です。

##Node.jsでBeacapp APIを呼び出す

Beacappに用意されているAPIは、今のところ、以下の2種類です。

* Beacon一括取得
* イベントログ取得

今回は、ログを集計したいので、「イベントログ取得API」を呼び出します。
必要なのは、以下の情報になります。

* 接続先URL:https://rest.beacapp.com/v1/log
* サブスクリプションID:BeacappログインURLにある8桁の文字列
	* https://cms.beacapp.com/cms/<font color="red">ABCD1234</font>/login
* API シークレットキー:(*お問い合わせください)

(オプション)

* リクエストトークン:アプリを指定して、ログを取得できます。
* datefrom:ログの開始日
* dateto:ログの終了日
* device_log_id:ログ取得開始地点を指定するシーケンスID（前回の最後のIDなどを指定する）


認証の方法は、ベースURLに、サブスクリプションIDとタイムスタンプを付与した文字列を、APIシークレットキーをキーにして、HMAC-SHA1で署名します。
できたコードをBase64エンコードしてPOSTすればOKです。
文字で書くと<font color="red">アレ</font>なので、実際のコードを載せておきます。

```Sample.js
var request = require('request');
var hmacsha1 = require('hmacsha1');

//Beacappの API URL
var beacappUrl = "https://rest.beacapp.com/v1/log";

//サブスクリプションID
var subscription_id = "ABCD1234";

//リクエストトークン
var request_token = "ASDFGHJKLSDFGHJKL";

//APIシークレットキー
var secretKey = "qwertyuiopasdfghjklzxcvbnm1234567890";

//Dateオブジェクトを作成
var date = new Date();

//現在のUNIX時間を取得する (秒単位)
var unixTimestamp = Math.floor(date.getTime() / 1000);

var signedURL = beacappUrl + "?subscription_id=" + subscription_id + "&timestamp=" + unixTimestamp;
var signature = hmacsha1(secretKey, signedURL); 

var auth = {};
var params = {};

  auth.subscription_id = subscription_id;
auth.signature = signature;
auth.timestamp = unixTimestamp;

params.request_token = request_token;
params.device_log_id = device_log_id;

params.datefrom = datefrom;
params.dateto = formatTime(new Date());
 var postData =
{
    auth: auth,
    params: params
    
}
 var options = {
    uri: beacappUrl,
    body: postData,
    json: true
};

var logs = []; request.post(options, function (error, response, body) {
    if (!error && response.statusCode == 200) {
        res.set('Content-Type', 'application/json');
        res.json({response:body});
        return;        
    } else {
        //error
        res.set('Content-Type', 'application/json');
        res.json({error:"error!!!"});
        return;
    }
});  
```

とりあえず、これでデータを受け取ることができます。

## APIをループさせる
Beacappのログは、アプリにもよりますが、多いと数百万〜数千万レコードにもなります。
それを一度に返却してしまうと、投げる方も受け取る方も、なかなか厳しいということで、100件ずつ返却する仕様となっています。
レスポンスに含まれる、最後のログIDを指定して、もう一度リクエストをすると、続きから100件取得できます。

ということで、2回目以降のリクエストには、前回のレスポンスが必要になります。
そこで、非同期処理を同期的に実行しなければいけません。
以下のSampleではyeildを使いました。
(setTimeOut()とSleepを駆使して、前回の処理が終わるのを待つという方法でも良いかもしれませんが、私は嫌です。)

```Sample.js

// npmのモジュール
var request = require('request');
var hmacsha1 = require('hmacsha1');
var co = require('co');


// BeaconIDをKeyにした連想配列にデータを入れることにする（最新のデータだけ必要なので）
var beacon_event_summary = {};
co(function*() {
        
        var continueFlag = true;
        var device_log_id = 0;
        while (continueFlag){
            //Beacappの API URL
            var beacappUrl = "https://rest.beacapp.com/v1/log";
            
            //サブスクリプションID
            var subscription_id = "ABCD1234";
            
            //リクエストトークン
            var request_token = "ASDFGHJKLSDFGHJKL";
            
            //APIシークレットキー
            var secretKey = "qwertyuiopasdfghjklzxcvbnm1234567890";
            
            //Dateオブジェクトを作成
            var date = new Date(); 

            //現在のUNIX時間を取得する (秒単位)
            var unixTimestamp = Math.floor(date.getTime() / 1000);
            
            var signedURL = beacappUrl + "?subscription_id=" + subscription_id + "&timestamp=" + unixTimestamp;
            var signature = hmacsha1(secretKey, signedURL);

            var auth = {};
            var params = {};

            auth.subscription_id = subscription_id;
            auth.signature = signature;
            auth.timestamp = unixTimestamp;

            params.request_token = request_token;
            if (device_log_id != 0) {
                params.device_log_id = device_log_id;
            }

            params.dateto = formatTime(new Date());

            var postData =
            {
                auth: auth,
                params: params
            }


            var options = {
                uri: beacappUrl,
                body: postData,
                json: true
            };


            yield new Promise(function (resolve, reject) {
                request.post(options, function (error, response, body) {
                    if (!error && response.statusCode == 200) {
                        if(body.count != 100){
                            continueFlag = false;
                        }
                        for (var i = 0;i < body.logs.length;i++){
                            var log = body.logs[i];
                            //1番はアクティベーションログなので、無視。
                            if (log.event_type != 1) {
                                continue;
                            }
                            //連想配列に、IDをキーとしてデータを投入
                            beacon_event_summary[log.beacon_id.toString()] = 
                                {type:type,timestamp:log.device_timestamp.toString()};
                            //次のリクエスト用に、IDを保管
                            device_log_id = log.device_log_id;
                        }
                        resolve();
                    } else {
                        continueFlag = false;
                        return reject(error);
                    }
                });
            });
        }
    });
    res.set('Content-Type', 'application/json');
    res.json({response: beacon_event_summary});
    return;
        
```

>Sampleではyeildを使いました。

ここを、もうちょっと補足しておきます。
おそらく、Node.jsでrequestを投げる時の基本的な書き方は以下のような感じになるんだと思います。

```yeildSample.js

co(function*() {
    // requestのbodyを受け取る
    var body = yield new Promise(function (resolve, reject) {
        //この中で、非同期処理を行う
        request.post(options, function (error, response, body) {
            if (error){
                reject();
            } else {
                // bodyを返却できる
                resolve(body);
            }
        });
    });
}).catch(function (err) {
    // rejectすると、ここに入ってくる。
    return;
});
//なんか後続処理とか。

```


それから、色んなところにAPIを非同期で投げて、最後の受け取るところだけ待ちたい！
というケースだと（あんまりなさそうですが。。。）、Promise.all();が使えます。
多分。

```Promise_allSample.js
//Promiseを入れておく配列
var promises = [];
co(function*() {
    //100回分のリクエストを生成する
    for (var i = 0;i < 100;i++){
        var p = new Promise(function (resolve, reject) {
            request.post(options, function (error, response, body) {
                if (error){
                    reject();
                } else {
                    resolve();
                }
            });
        });
        //Promiseを溜めていく
        promises.push(p);
    }
    //一気に実行する（してる？）
    yield Promise.all(promises);
}).catch(function (err) {
    // rejectすれば、ここに
    return;
});
//あと、後続処理とか。

```


##集計画面を返す
そんなこんなで取得した、サマリ情報をテーブルとかで表示してあげれば完成。
認証とか、オシャレさとかそういうのを抜きにして、とりあえずテーブルを返すだけなら下記のような感じでできます。
もしプロジェクトでこんな書き方したら、大変な騒ぎになりそう。。。

でも、ここでjadeやら、ejsについては書くスペース（と気力）がないので割愛します。

```Response.js
var responseString = "<html>" +
    "<head>" +
    "<meta charset=\"utf-8\">" +
    "</head>" +
    "<body>" +
    "<table  border=\"1\">" +
    "<tr>" +
    "<th>Beacon ID</th><th>TimeStamp</th><th>Event Type</th>" +
    "</tr>" +

    
for (var key in beacon_event_summary){
	responseString += "<tr>" + 
	    "<td>"+ key + "</td>" +
    	"<td>" + beacon_event_summary[key].timestamp + "</td>" +
	    "<td>" + beacon_event_summary[key].type + "</td>" +
	    "</tr>";
	    
}
responseString += "</table>" +
    "</body>" +
    "</html>";

res.write(responseString);

return;

```

とりあえず、これで、それぞれのBeaconが最後にいつ検知されたかが分かりますね！

>衝撃的なUI...
>![衝撃のUI.png](https://qiita-image-store.s3.amazonaws.com/0/130138/766be267-094a-cdfa-104f-77ff273114ae.png "衝撃のUI")


例えば、Beaconの電池切れとか、全然人が近づかない場所とかが分かるかもしれません。

集計の中で、カウントとかをしておけば、ヒートマップも作れちゃいます。

さらに、認証機能を作ったり、結果をDBに入れておいたりすることで、どんどん使いやすくしていきたいところ。
Bluemixには、その辺のサービスもたくさんあるので、また紹介していければと思います。