SoracomのAPIを介してSIMの通信状況を監視する

# はじめに
IoT的なものを作ろうと思ってモバイルネットワークを使うとき、機器の監視は大変頭を悩ませるポイントだと思っています。
クラウドと端末を閉域で結ぶサービス([Soracom Canal](https://soracom.jp/services/canal/)等)もありますが、個人でやるにはちょっと高いですね。
今回はSoracom Airを使うことを前提に、Soracom APIを叩くことによりSIMの通信状態の監視を行います。

# やったこと

![スクリーンショット 2016-11-03 20.49.59.png](https://qiita-image-store.s3.amazonaws.com/0/99704/8c484f21-02a7-2005-a22c-5ae4a70a69fd.png "スクリーンショット 2016-11-03 20.49.59.png")

こんな感じにしてみました。
今回センサーはSensorTagを使っているので、Bluetoothが切れたときは直接SES空メールを送信していますが、そちらは本稿では割愛します。
赤い線で繋いでいる側を解説します。

# 詳解
## Soracom APIの叩き方
まずはSoracom APIを叩いてみましょう。
何にせよ、Soracomが提供しているAPIドキュメントが良い感じです。
APIキーの入手もテストもここからできます。

[Soracom APIリファレンス](https://dev.soracom.io/jp/docs/api/)

まずはAPIキーを入手します。
認証キーによる認証とユーザコンソールのアカウントによる認証の2種類がありますが、認証キーによる認証は多分僕の場合関係ないので、右側を入力して、認証を押します。
そうすると、API キーが入手できます。

![edited.png](https://qiita-image-store.s3.amazonaws.com/0/99704/c85d2e5e-a698-6c64-f403-a15350f47c14.png "edited.png")

この認証をかけたあとは、下側にあるサンプルが使い放題です。
とりあえず今回は、Subscriber内のGET /subscriber/{imsi}を利用します。

![スクリーンショット 2016-11-03 21.00.25.png](https://qiita-image-store.s3.amazonaws.com/0/99704/b8749d5f-54e0-2fac-ee97-0687e4503de7.png "スクリーンショット 2016-11-03 21.00.25.png")

その前に、imsiの値はSoracom管理コンソールの下記を見て控えておいてください。

![edited2.png](https://qiita-image-store.s3.amazonaws.com/0/99704/aa661dba-d57d-f35d-23aa-453ce724fceb.png "edited2.png")


それでは試してみましょう。
Parameters内にimsiを入力するBOXがあるので、そこに値を入力したあと、「Try it out!」ををクリックします。
![edited3.png](https://qiita-image-store.s3.amazonaws.com/0/99704/3f81fe99-4f0a-1fe4-031f-5d62c29bb6d2.png "edited3.png")

そうすると、response以外にもcurlだったらこうすればいいといったようなサンプルも表示されます。大変親切でこう書かれると文書よりもわかりやすくていいですね。

![edited4.png](https://qiita-image-store.s3.amazonaws.com/0/99704/20ca9a51-efdf-72ba-e0fb-da28f0f71fb8.png "edited4.png")

認証のパラメータとしてX-Soracom-API-Key, X-Soracom-Tokenの2つを渡してやればいいようですね。あと、Accept: application/json。

そしてResponseですが、今回はその中のsessionStatus.onlineに注目します。

![edited5.png](https://qiita-image-store.s3.amazonaws.com/0/99704/9ed8062a-1168-ac43-77f9-6fc977469339.png "edited5.png")


いま接続状態なのでtrueですが、切れるとfalseになります。
これを利用して監視します。

## Lambdaのコード
僕はプログラマーではないのであまり得意な言語はないので、正直なんでもいいんですが、とりあえずnode.jsを使います。多少慣れてるので。

色々とハードコートされててあれですが、サンプルですのでご容赦を。
実際にはKMSとか使ってますが、ここでそれを書くと長くなるので短縮版にしてます。

```javascript:index.js
exports.handler = function (event, context, callback) {
    var https = require('https');
    var AWS = require('aws-sdk');

    var imsi = 'PUT YOUR IMSI';
    var to_mail_addr = 'PUT YOUR MAIL ADDRESS YOU RECIEVED ALERT MAIL';
    var from_mail_addr = 'PUT YOUR MAIL ADDRESS YOU SEND ALERT MAIL(SES)';

    var soracom_key = 'PUT YOUR SORACOM API KEY'
    var soracom_token = 'PUT YOUR SORACOM API TOKEN'

    var aws_key = 'PUT YOUR AWS API KEY TO SEND MAIL FROM SES'
    var aws_sec = 'PUT YOUR AWS API SECRET KEY TO SEND MAIL FROM SES'

    var options = {
        hostname: 'api.soracom.io',
        path: '/v1/subscribers/' + imsi,
        method: 'GET',
        headers: {
          'X-Soracom-API-Key': soracom_key,
          'X-Soracom-Token': soracom_token
        }
    };
    var req = https.request(options, (response) => {
        var buf = "";
        response.on('data', (chunk) => {
            buf = buf + chunk;
        })
        response.on('end', () => {
            const body = JSON.parse(buf);
            if (body.sessionStatus.online != true) {
                var ses = new AWS.SES({apiVersion: '2010-12-01',
                    accessKeyId: aws_key,
                    secretAccesskey: aws_token,
                    region: 'us-east-1'
                });
                var params = {
                    Destination: {
                        ToAddresses: [ to_mail_addr ]
                    },
                    Message: {
                        Body: {
                            Text: {
                                Data: 'SIM(' + body.imsi + ') is offline',
                                Charset: 'utf-8'
                            }
                        },
                        Subject: {
                            Data: 'SIM error occured',
                            Charset: 'utf-8'
                        }
                    },
                    Source: from_mail_addr
                };
                ses.sendEmail(params, function (err, data) {
                    if (err) { callback ('e', err); }
                    else { callback(null, data); }
                });
            }
        });
    });
    req.end();
}
```

たぶんプログラマの諸兄なら読めるでしょうが、やってることは単純で、Soracom APIを叩いて、結果次第でSES経由でメールを出しているだけです。

外部ライブラリはaws-sdkのみなので、このコードをそのまま貼り付ければOKですね。

## Lambda関数への登録
Lambda関数への設定は至極一般的なものなので、サクッと説明します。

Create Lambda functionをクリック
![edited6.png](https://qiita-image-store.s3.amazonaws.com/0/99704/5ee8b862-3885-619a-7c8c-157d4dc19d3b.png "edited6.png")

BluePrintはBlank
![edited7.png](https://qiita-image-store.s3.amazonaws.com/0/99704/a539a189-ac98-9556-0c27-0bc5c5debf7b.png "edited7.png")

triggerはCloudWatch　Events - Schedule
![edited8.png](https://qiita-image-store.s3.amazonaws.com/0/99704/5a162e00-3d16-1d0b-a703-9efbf184f25c.png "edited8.png")

ここはなんとなく良い感じのスケジュールを選べば良いと思います。
![edited9.png](https://qiita-image-store.s3.amazonaws.com/0/99704/5fcdacda-855d-aa3c-d530-893b2fc496a9.png "edited9.png")

コードは適当に貼り付ければOKです。
![edited10.png](https://qiita-image-store.s3.amazonaws.com/0/99704/e345a14b-19e2-b23b-6be0-9745cebfb8fb.png "edited10.png")

外部APIを叩くので、タイムアウト値は長めに。
ユーザは前に作っていたものを使ってしまったのであまり良く覚えていないですが、たしかこの画面で新規ロールを作って、IAMの画面からSESのFull権限を追加しました。
![edited11.png](https://qiita-image-store.s3.amazonaws.com/0/99704/7674c559-e0ee-368f-d217-cb7b509ef3c2.png "edited11.png")

以上です。

## 問題点
この方法の問題点はきれいに切れた場合はちゃんと検知できるものの、ブチッといくとうまくいかない点ですね。。。
(たまたまブチッと切ってみたらうまくいかないことに気づいた)

そんなわけで問題を解決してみます。

# 問題解決
## 問題解決版で使うAPI
便利なもので、5分単位の通信量が見れるAPIがあるのでそれを利用します。

![スクリーンショット 2016-11-03 23.22.17.png](https://qiita-image-store.s3.amazonaws.com/0/99704/2b80bebe-457a-0f25-eb9e-47614199ada4.png "スクリーンショット 2016-11-03 23.22.17.png")

まず試しにこんなパラメータで投げてみます。
これは2016/11/3の日本時間19:15-19:30くらいを指定しています。
![edited12.png](https://qiita-image-store.s3.amazonaws.com/0/99704/a929a66d-0345-005e-0482-d83d50b1e1b1.png "edited12.png")

その時のレスポンスです。
このときは通信していたので、何となくそれらしい値が返ってます。
![スクリーンショット 2016-11-03 23.24.48.png](https://qiita-image-store.s3.amazonaws.com/0/99704/6d0758d3-c9f2-4179-d5bc-21f243afe815.png "スクリーンショット 2016-11-03 23.24.48.png")

では、直近で切れている2016/11/3の日本時間23:00 - 23:15で取得してみましょう。

![スクリーンショット 2016-11-03 23.27.19.png](https://qiita-image-store.s3.amazonaws.com/0/99704/5756b74c-ab1f-3ec1-667e-ed00584472fb.png "スクリーンショット 2016-11-03 23.27.19.png")

[]が返ってきますね！
これを利用すれば通信の有無が見れそうです。

## 問題解決版のコード

```javascript:index.js
exports.handler = function (event, context, callback) {
    var https = require('https');
    var AWS = require('aws-sdk');

    var imsi = 'PUT YOUR IMSI';
    var to_mail_addr = 'PUT YOUR MAIL ADDRESS YOU RECIEVED ALERT MAIL';
    var from_mail_addr = 'PUT YOUR MAIL ADDRESS YOU SEND ALERT MAIL(SES)';

    var soracom_key = 'PUT YOUR SORACOM API KEY'
    var soracom_token = 'PUT YOUR SORACOM API TOKEN'

    var aws_key = 'PUT YOUR AWS API KEY TO SEND MAIL FROM SES'
    var aws_sec = 'PUT YOUR AWS API SECRET KEY TO SEND MAIL FROM SES'

    var to = new Date().getTime();
    var from = to - 1000 * 15 * 60;
    console.log(from, to);
    var options = {
        hostname: 'api.soracom.io',
        path: '/v1/stats/air/subscribers/' + imsi + '?from=' + from + '&to=' + to + '&period=minutes',
        method: 'GET',
        headers: {
          'X-Soracom-API-Key': soracom_key,
          'X-Soracom-Token': soracom_token
        }
    };
    var req = https.request(options, (response) => {
        var buf = "";
        response.on('data', (chunk) => {
            buf = buf + chunk;
        })
        response.on('end', () => {
            const body = JSON.parse(buf);
            if (body.length == 0) {
                var ses = new AWS.SES({apiVersion: '2010-12-01',
                    accessKeyId: aws_key,
                    secretAccesskey: aws_key,
                    region: 'us-east-1'
                });
                var params = {
                    Destination: {
                        ToAddresses: [ to_mail_addr ]
                    },
                    Message: {
                        Body: {
                            Text: {
                                Data: 'SIM(' + imsi + ') is offline',
                                Charset: 'utf-8'
                            }
                        },
                        Subject: {
                            Data: 'SIM error occured',
                            Charset: 'utf-8'
                        }
                    },
                    Source: from_mail_addr
                };
                ses.sendEmail(params, function (err, data) {
                    if (err) { callback ('e', err); }
                    else { callback(null, data); }
                });
            }
        });
    });
    req.end();
}
```

基本は一緒ですね。

# まとめ
今回はSoracomのAPIを使ったSIMの監視についてまとめてみました。
まぁ、とりあえずこれでできますよというアイディアレベルで作ってみたので、ちょっと今後運用してみて、何か思う所があれば再度考えます笑

機器自体のステータスなんかは見れませんが、とりあえず通信できている/できていないレベルであればこんな見方もできます、というところでいかがでしょうか。