セッション On/Off トリガーでデバイスの状態検知して見える化してみる

本記事は[SORACOM Advent Calendar 2020]("https://qiita.com/advent-calendar/2020/soracom")の2020/12/01分です。

#### セッション On/Off トリガーはまだLimitedPreviewなので使える人も限られますし、そもそも基本業務用（のはず）
#### また、まだLimitedPreview段階なので将来どうなるかはSORACOMの中の人しかわからなので仕様とか色々変わってもNCで
#### 繰り返しになりますが基本業務用（のはず）

## 2020/12/1 16:30追記
SORACOM公式さんから*${sessionStatus}*を使うと1メソッドでできるよって教えてもらいました！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">また、セッション状態ですが、こちらは ${sessionStatus} という変数を利用するとセッション作成時に「Created」、セッション削除時に「Deleted」が入りますので、Lambda側でこの変数を利用して状態を判断することができます。記載が漏れており申し訳ございません。是非お試しください😊</p>&mdash; ソラコム公式 (@SORACOM_PR) <a href="https://twitter.com/SORACOM_PR/status/1333674077676965889?ref_src=twsrc%5Etfw">December 1, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Gazyuさん、早速SORACOMアドベントカレンダー1日目の記事投稿ありがとうございます！呼び出しロールの記載不足について、ブログにて情報を追加いたしました。ご指摘いただきありがとうございます。<a href="https://t.co/KX6clqhLe1">https://t.co/KX6clqhLe1</a></p>&mdash; ソラコム公式 (@SORACOM_PR) <a href="https://twitter.com/SORACOM_PR/status/1333673837888634881?ref_src=twsrc%5Etfw">December 1, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 前提
- 業務で大量（万単位)のIotデバイスを管理している方
- 設置場所が電波的に怪しい場所が多い方
- Iotデバイスの状態がなるべくリアルタイムに知りたい方

なんかが対象かと思います。

# ゴール
セッション状態をAWS Lambdaに通知してデータの保持だったり色々できるようになる。
今回はDynamoDBに現在のSIMのセッション状態のステータスを保持できるようにします。

# セッション On/Off トリガーとは？
[セッション On/Off トリガーは]("https://soracom.jp/services/air/session-event-handler-limited-preview/")SIMのセッション状態（要はオンライン・オフライン）が変わった場合に、SORACOMでイベントハンドラを実行できるようになる機能です。
これまで、SIM1枚1枚の現在のセッション状態を知るには、SORACOM API経由で状態の取得をするしかありませんでした。

しかし、API経由の場合には実行回数制限があり数枚のSIMなどであれば繰り返しAPI実行をしても問題ありませんが、業務で使用するような枚数になると現実的ではありませんでした。

そこで出てきたのがセッション On/Off トリガーです。
SIMのセッション状態が変わると自動的にSORACOMシステム側からイベントハンドラで通知してくれるので一回設定すればあとは待っているだけでいいのです。

### あなたが神か！

# 他の方法はないの？
無いわけではありません。

例えば、Iotデバイスから定期的にログを送り、サーバ側でログの数をカウントすることで似たようなこともできたりします。
しかし、

1. ヘルスログを送るための仕組みがIotデバイス側に必要になる
2. ログデータの解析基盤が必要になる
3. ログが途絶えた場合に圏外になったのかIotデバイスのSWのバグかがすぐに分かりづらい
4. リアルタイム性とログの送信間隔がトレード・オフの関係
5. その他

などのハードルがあるため、ログによるヘルスチェックもありつつ更にセッション On/Off トリガーがあると便利です。


# 実際にやってみる！
## 1. セッション On/Off トリガーの利用申請
[セッション On/Off トリガー、IMEI ロックアクション イベントハンドラー LimitedPreview ご利用申請]("https://soracom.jp/services/air/session-event-handler-limited-preview/")から利用申請をします。

[LimitedPreview]("https://soracom.zendesk.com/hc/ja/articles/217805308")なので、もしだめだった場合には素直に諦めしょう。

## 2. 案内に従い初期設定
申請が通った場合には、メールでセッション On/Off トリガーを使うための初期設定や制限事項が送られてきます。
メールの記載内容に従い設定しましょう。
ここは特にNDAとか書かれてなかったような気がしますがオープンなサービスではないので詳細は記載しません。
ただ、基本的に初期設定は非常に簡単で、特に使いだしに困ることはなかったです。

## 3. AWSの認証情報登録
[イベントハンドラー機能の AWS Lambda アクションの認証情報に IAM ロールが指定可能に]("https://blog.soracom.com/ja-jp/2020/10/13/event-handler-updates-credentials-using-iam-role/")を参考にAWS LambdaをSORACOMイベントハンドラーで呼び出せるように設定をします。
基本このページの**ユーザーコンソールの設定**までを設定すれば大丈夫です。

## 4. DynamoDBを用意
AWSコンソールで、DynamoDB->テーブル->テーブルの作成と進みテーブルを作ります。
![screencapture-ap-northeast-1-console-aws-amazon-dynamodbv2-home-2020-12-01-09_48_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45441/45f05142-674d-4a83-15bd-ecb16a8ad363.png)

テーブル名は適当で、パーティションキーは今回はSIMのIMSIにしました。セッションステータス(session-status)なんかでもいいと思います。
ここら辺は各々で

**テーブルの作成**を押してテーブルを作成します。

作成まで多少時間がかかるのでその間に次のステップに進みます。

## 5. 呼び出されるLambda関数を用意
AWSコンソールで、Lambda->関数->関数の作成と進み関数を作ります。
![screencapture-ap-northeast-1-console-aws-amazon-lambda-home-2020-12-01-09_54_40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45441/b38a1893-ddbe-9a58-5fe0-b5fbe4ed5750.png)
関数名は適当で、ランタイムはPythonがboto3でAWSの機能が楽に使えて便利なので僕はよく使います。

**ここでの注意点は、セッション On/Off トリガーは、イベントハンドラでLambdaコールされた際にセッション状態自体は呼び出し時に渡されるデータに入れてくれないということです。**
**どういうことかと言うと、On/Offの状態を取りたい場合には、**
1. **状態がOnになった時にキックされるイベントハンドラ&Lambda関数**
2. **状態がOffになった時にキックされるイベントハンドラ&Lambda関数**
**の2つを用意する必要があるということです。**

上記の画像はOnになった時のLambda関数を作っています。

関数が作れたら、アクセス権限タブを開き、実行ロールへ移動します。
![スクリーンショット 2020-12-01 10.18.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45441/47ea57c0-7692-398a-bd73-35ea226b24df.png)

ロールのポリシーでDynamoDBへのアクセス権を付与します。

````json
        {
            "Sid": "DyanmoDb",
            "Effect": "Allow",
            "Action": "dynamodb:*",
            "Resource": "arn:aws:dynamodb:ap-northeast-1:xxxxxxxxxxx:table/SoracomSessionStatus"
        }
````

今回はお試しなのでDBに対して何でもできてしまいますが、実際にはちゃんと設定したほうがいいです。

あとは、Lambda関数で下記のコードを関数コードとして設定します。

````python
import json
import boto3
from datetime import datetime as dt

table_name = 'SoracomSessionStatus'

dynamo = boto3.client('dynamodb')


def lambda_handler(event, context):
    print(event)
    imsi = event['imsi']
    tdatetime = dt.now()
    item = { 
        'imsi': {'S': imsi},
        'status': {'S': 'online'},
        'update_at':{'S': tdatetime.strftime('%Y-%m-%d %H:%M:%S')}
        
    }
    
    dynamo.put_item(TableName=table_name, Item=item)

    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

````

lambda_handlerの頭でeventの中身をprintしておくと、CloudWatchのログに渡ってくるデータが残るので後でデバッグが楽です。
~~公式docのどこにもセッション On/Off トリガーの時のデータ形式が書かれてなかったのでちょっと困りました~~

上記はセッションがOn（つまりオンライン)になった時の関数なので、コードの中のonlineをofflineに変えたメソッドをもう一個作ります。
ポリシーの設定方法や設定内容は一緒です。

## 6. SORACOMからの呼び出しに使われるロールにLambda実行権限を付与
"3.AWSの認証情報登録"で用意したSORACOMからの呼び出しに使われるロールにLambda実行権限を付与します。
これがついてないとイベントハンドラが発火してもLambda関数が実行されません。

AWSコンソールでIAM->ロール->該当のロールと進み、新しいポリシーをこのロールに追加します。

````json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Lambda",
      "Effect": "Allow",
      "Action": [
        "lambda:InvokeFunction"
      ],
      "Resource": [
        "arn:aws:lambda:ap-northeast-1:xxxxxx:function:soracom-session-online", 
        "arn:aws:lambda:ap-northeast-1:xxxxxx:function:soracom-session-offline"
      ]
    }
  ]
}
````

## 7. イベントハンドラ作成
SORACOMコンソールでmenu->イベントハンドラー->イベント作成と進みます。
![screencapture-console-soracom-io-2020-12-01-10_43_35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45441/aceb7dc2-ffb7-1b89-9718-9eb7a64b3276.png)

- イベント名は適当に
- 監視対象は"グループ"にします。（現状はグループのみという案内があった）
- 監視対象の空欄には"グループID"を入れます。(SIMグループ一覧などでIDとなっているハッシュっぽい文字列)
- ルールには、"SIMのセッションステータスが変更されたら実行"と"Created"を選択します。
- "Created"がSIMがオンラインになった場合で、"Deleted"がオフラインになった場合の設定です。
- アクションはAWS Lambdaを呼び出すを選択
- ENDPOINTはAWSの東京リージョンを使っている場合には *https://lambda.ap-northeast-1.amazonaws.com*
- FUNCTION NAMEは作ったLambda関数の名前（今回はsoracom-session-online）
- 認証情報IDは"3. AWSの認証情報登録"で作ったものを選択

最後に保存を押せば設定完了です。

これはオンラインになった時のイベントハンドラなので、オフラインになった場合も同じようにつくります。

これで設定はすべて完了です。

あとは待っていると、SIMのセッション状態が変わるたびにDynamoDBに登録されていきます。

# 実際の結果
![スクリーンショット 2020-12-01 11.08.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45441/37aa1a92-2662-3be8-df5a-904b5337d260.png)



# まとめ
- 申請以外の場所は基本的に通常のイベントハンドラでLambda関数を呼び出すのと同じ用に使える **すごい便利** 
- 今回はDynamoDBへのデータ投入だけだったけれどもこのあとAWS SNS使って通知などもできる **すごい便利**
- リアルタイムに情報更新される！ **すごい便利**
- SORACOMからの呼び出しロールにlambda:InvokeFunctionを付与するのは公式Docに書かれてない気がするしハマりどころなので注意


# 補足
## これステータス変更されるまでDBに反映されませんか？
Yes!
なので、最初はAPIとかでがっとSIMの一覧とステータスの一覧を取得してDynamoDBに入れておく必要がある

このコマンドでSIMのIMSIと直近のステータスを抜き出せるのでこれをどうにかして入れるといいんじゃないかな？

```sh
 soracom subscribers list --fetch-all   |jq -r ".[] |.imsi,.sessionStatus.online" 
```

## リアルタイムですか？
かなりリアルタイム！すごい
けど、オフラインになった時に通知までに時間がかかることがある（コンソールでもオンラインのままなので多分LTEとかの根本的なところの仕様だと思う）