Meraki Scanning API 経由で取得した WiFi 位置情報を AWS へ保存する（API Gateway + Lambda + Dynamo DB）

[こちらの元ネタ](https://github.com/dexterlabora/cmxreceiver-lambda-inline)の手順を実施した時の作業メモです。あくまでもテスト用なので最適設定の保証はありません。権限設定には特にご注意下さい。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/104747/375cda86-cdd5-ea92-3c6a-99c83f3c4b3c.png)


# Meraki API の概要
Meraki はクラウド型 WiFi サービスで、各所に配置された Meraki アクセスポイント（AP）が WiFi 端末や BLE 端末の送信するプローブリクエストを収集して端末の位置を算出する機能を持つ。この位置情報は Meraki クラウドから Scanning API を経由して取得可能（これに対して Meraki Dashboard の情報へアクセスするための API を Dashboard API と呼び、使い方が異なる）。

## 位置情報取得に必要なもの
Scanning API を使うには、インターネットからアクセスできる場所に自前でサーバを用意する必要がある。設定を正しく完了すると Meraki クラウドは自前サーバに対して位置情報を JSON 形式で周期的（30-120秒程度）に POST する。

今回は自前サーバの代わりに Amazon API Gateway + Lambda を使用する。Meraki クラウドから位置情報を受けた Lambda は最終的に DynamoDB へデータを保存する。

なお、Meraki クラウドはプライバシーの問題が考慮されており、端末識別には MAC アドレスと IP アドレスを使うだけで、個人の識別は行わない。行いたい場合には MAC アドレス/IP アドレスと個人の情報を何らかの方法で別途ひもづける必要がある。が、ここではひもづけ方法については言及しない。

位置情報は AP 毎に以下の JSON フォーマットで取得可能：

```
{
  "apMac": <string>,
  "apTags": [<string, ...],
  "apFloors": [<string>, ...],
  "observations": [
    {
      "clientMac": <string>,
      "ipv4": <string>,
      "ipv6": <string>,
      "seenTime": <string>,
      "seenEpoch": <integer>,
      "ssid": <string>,
      "rssi": <integer>,
      "manufacturer": <string>,
      "os": <string>,
      "location": {
        "lat": <decimal>,
        "lng": <decimal>,
        "unc": <decimal>,
        "x": [<decimal>, ...],
        "y": [<decimal>, ...]
      },
    },...
  ]
}
```

# 設定方法
## Meraki の設定
Scanning API を利用するには、Meraki コンソールにて API を enable にした後、"validator" と "secret" を入手し、それらの値を Lambda のプログラムへ入力する必要がある。Meraki コンソールでの手順は以下の通り：
１．Meraki ダッシュボードへアクセス：http://dashboard.meraki.com/
２．"Network-wide" > "CONFIGURE" | "General" をクリック
３．"Location and scanning" 内の項目を以下の通り設定：

* "Analitics" : "Analitics enabled"
* "Scanning API" : "Scanning API enabled"
* "Validator" : （メモを取る。後ほど Lambda の設定に必要）
* "Post URLs" : "Add a Post URL" をクリック。行が追加されたら、"Post URL" はひとまず空欄にしておく。"Secret" は Lambda との認証で使うパスワードを決めて入力する（こちらも同じくメモを取る）
<img width="876" alt="Meraki9.png" src="https://qiita-image-store.s3.amazonaws.com/0/104747/f6be5424-396a-7514-c30e-f89e30a535de.png">


４．右下に現れる "Save" をクリック


## AWS Dynamo DB テーブルの作成
以下の手順でDynamo DBテーブルを作成する：
１．AWS マネジメントコンソールにログインして DynamoDB コンソールを開く
２．「テーブルの作成」をクリック
３．「テーブル名」に "cmxdata" と入力
４．「パーティションキー」に "message_id" と入力
５．「ソートキーの追加」にチェックを入れ「ソートキー」に "message_ts" と入力
６．「デフォルト設定の使用」はそのままチェックを入れておく
７．「作成」をクリック

以上で cmxdata という名の Dynamo DB が作成される。

## Amazon API Gateway の作成
以下の手順で AWS Gateway を作成する。
１．AWS マネジメントコンソールにログインして Amazon API Gateway コンソールを開く
２．「APIの作成」をクリック（初回は「今すぐ始める」＞「サンプルAPIの作成」＞「OK」）
３．「新しいAPI」を選択
４．「API名」に "CMX"、「説明」に "Cisco Meraki CMX Receiver" と入力（省略可）
５．「APIの作成」をクリック
<img width="770" alt="API1.png" src="https://qiita-image-store.s3.amazonaws.com/0/104747/d71a5d71-6382-59b9-0a5e-8d40ac4c0c1f.png">

以上で CMX という名の API Gateway が作成される。

## IAMロールの作成
Lambda を設定する前に IAM ロールを作成しておく。Lambda は DynamoDB へ書込みを行うので、それを許可するロールを以下の手順で作成する：
１．AWS マネジメントコンソールにログインして IAM コンソールを開く
２．「ロール」＞「ロールの作成」をクリック
３．「AWSサービス」＞「Lambda」＞「次のステップ：アクセス権限」をクリック
<img width="749" alt="IAM2.png" src="https://qiita-image-store.s3.amazonaws.com/0/104747/b2cf56b2-20c7-7fd0-f765-2931148e736d.png">

４．検索ボックスに "dynamodb"と入力すると、マッチした既存ポリシーが検索結果として表示されるので "AmazonDynamoDBFullAccess" にチェックを入れ「次のステップ：確認」をクリック
<img width="775" alt="IAM3.png" src="https://qiita-image-store.s3.amazonaws.com/0/104747/dd9d9c88-533f-2a26-a476-982ee149a1cf.png">

５．「ロール名」に "cmxreceiver-lambda-executor" と入力し「ロールの作成」をクリック
<img width="745" alt="IAM4-2.png" src="https://qiita-image-store.s3.amazonaws.com/0/104747/6ef0e6b0-42d0-494b-ef8e-0f6f41d810f5.png">


以上で cmxreceiver-lambda-executor という名の IAM ロールが作成される。

## AWS Lambda の作成
以下の手順で AWS Lambda 関数を作成する。
１．AWS マネジメントコンソールにログインして AWS Lambda コンソールを開く
２．「関数の作成」をクリック
３．設計図について、「一から作成」をクリック
４．「名前」に "cmxreceiver-dynamodb" と入力。「ロール」は「既存のロールを選択」、「既存のロール」は先ほど作成した「cmxreceiver-lambda-executor」を選択し、「関数の作成」をクリック。
<img width="795" alt="Lambda5.png" src="https://qiita-image-store.s3.amazonaws.com/0/104747/a5a499a4-48c8-e6cf-7190-d561e0116814.png">

５．関数の作成に成功したら、「設定」タブ内の「ランタイム」に "Node.js 4.3" を選択し、「関数コード」欄のコードを、[こちら](https://github.com/dexterlabora/cmxreceiver-lambda-inline/blob/master/src/cmx-lambda-inline.js)のソースコードで置き換える。DynamoDB へ書込むデータフォーマットはこのコードが決めている。
<img width="670" alt="Lambda7.png" src="https://qiita-image-store.s3.amazonaws.com/0/104747/4b5ce59b-8d12-defb-ba5a-0af5304742e6.png">


６．置き換えたソースコードの "secret" 及び "validator" の値を先ほど「Meraki の設定」でメモした値へ置き換える

```
26 var secret = "ここを secret で置き換える"; //"enterYourSecret";
27 var validator = "ここを validator で置き換える";//"enterYourValidator";
```

７．「トリガー」タブをクリックし、「トリガーを追加」をクリック
８．矢印左側の点線の四角をクリックし、「API Gateway」を選択
９．「API名」には "CMX" を、「デプロイされるステージ」には "prod" を、「セキュリティ」には「オープン」を選択し、「送信」をクリック
１０．cmxreceiver-dynamodb という名の Lambda 関数が無事作成されたことを確認した後、"API Gateway: CMX" の下に表示されている三角（メソッドの左側）をクリックする。「URLの呼び出し」にこの Lambda 関数を呼び出すための URL が記載されているのでメモする（後ほど Meraki の Post URLs へ入力する）。
<img width="461" alt="Lambda8.png" src="https://qiita-image-store.s3.amazonaws.com/0/104747/96392249-388e-adac-552d-3a4c660fbccd.png">



## Lambda の動作確認
１．「テストイベントの選択」をクリックして「テストイベントの設定」を選択
２．「テストイベントの設定」画面が現れるので、「イベント名」は "GETtest" とし、JSON の記述を以下の様に書き換える：

```
{
  "httpMethod": "GET"
}
```

３．「作成」をクリック
４．「保存してテスト」をクリック
５．実験結果に「成功」が表示されることを確認。「詳細」をクリックしてログを開き、"body" が validator の値となっていることを確認する。

<img width="647" alt="Lambda_verify.png" src="https://qiita-image-store.s3.amazonaws.com/0/104747/9db3958d-9698-8963-1b7f-fad2087737d9.png">


６．再び「テストイベントの設定」を選択してクリック
７．JSON の記述を以下の様に書き換える（[ソースコードはこちら](https://github.com/dexterlabora/cmxreceiver-lambda-inline/blob/master/src/cmx-post-test.json)）：

```

{
  "httpMethod": "POST",
  "body": "{\"version\":\"2.0\",\"secret\":\"supersecret\",\"type\":\"DevicesSeen\",\"data\":{\"apMac\":\"00:18:0a:13:dd:b0\",\"apFloors\":[\"Lounge\"],\"apTags\":[\"\",\"home\",\"\"],\"observations\":[{\"ipv4\":\"/192.168.0.38\",\"location\":{\"lat\":51.53569388164768,\"lng\":-0.06959447280598852,\"unc\":0.6844347949152217,\"x\":[3.4765536752229584],\"y\":[4.354354228797782]},\"seenTime\":\"2016-10-04T15:40:46Z\",\"ssid\":\".interwebs\",\"os\":\"Eye-Fi Wireless Memory Card\",\"clientMac\":\"18:fe:34:fc:5a:7f\",\"seenEpoch\":1475595646,\"rssi\":50,\"ipv6\":null,\"manufacturer\":\"Espressif\"},{\"ipv4\":\"/192.168.0.35\",\"location\":{\"lat\":51.53569388164768,\"lng\":-0.06959447280598852,\"unc\":2.971641173621407,\"x\":[3.4765536752229584],\"y\":[4.354354228797782]},\"seenTime\":\"2016-10-04T15:41:21Z\",\"ssid\":\".interwebs\",\"os\":\"Eye-Fi Wireless Memory Card\",\"clientMac\":\"18:fe:34:f2:95:26\",\"seenEpoch\":1475595681,\"rssi\":42,\"ipv6\":null,\"manufacturer\":\"Espressif\"},{\"ipv4\":\"/192.168.0.95\",\"location\":{\"lat\":51.53570062829968,\"lng\":-0.06963152908846837,\"unc\":10.952635148276672,\"x\":[6.134169212816191],\"y\":[4.10713358312542]},\"seenTime\":\"2016-10-04T15:40:48Z\",\"ssid\":\".interwebs\",\"os\":\"Generic Linux\",\"clientMac\":\"74:da:38:88:7c:df\",\"seenEpoch\":1475595648,\"rssi\":41,\"ipv6\":null,\"manufacturer\":\"Edimax Technology\"},{\"ipv4\":\"/192.168.0.92\",\"location\":{\"lat\":51.53569388164768,\"lng\":-0.06959447280598852,\"unc\":0.08441728766877274,\"x\":[3.4765536752229584],\"y\":[4.354354228797782]},\"seenTime\":\"2016-10-04T15:41:21Z\",\"ssid\":\".interwebs\",\"os\":null,\"clientMac\":\"18:fe:34:ce:a1:6d\",\"seenEpoch\":1475595681,\"rssi\":53,\"ipv6\":null,\"manufacturer\":\"Espressif\"},{\"ipv4\":null,\"location\":{\"lat\":51.5357040016257,\"lng\":-0.06965005722969408,\"unc\":30.97719405846896,\"x\":[7.462976981175844],\"y\":[3.983523257388808]},\"seenTime\":\"2016-10-04T15:41:02Z\",\"ssid\":null,\"os\":null,\"clientMac\":\"00:c2:c6:71:8e:64\",\"seenEpoch\":1475595662,\"rssi\":25,\"ipv6\":null,\"manufacturer\":\"Intel\"},{\"ipv4\":null,\"location\":{\"lat\":51.5357040016257,\"lng\":-0.06965005722969408,\"unc\":33.80737822838868,\"x\":[7.462976981175844],\"y\":[3.983523257388808]},\"seenTime\":\"2016-10-04T15:41:21Z\",\"ssid\":null,\"os\":null,\"clientMac\":\"d8:49:2f:07:75:e5\",\"seenEpoch\":1475595681,\"rssi\":28,\"ipv6\":null,\"manufacturer\":\"Canon\"},{\"ipv4\":\"/192.168.0.15\",\"location\":{\"lat\":51.53569388164768,\"lng\":-0.06959447280598852,\"unc\":4.4709491263240615,\"x\":[3.4765536752229584],\"y\":[4.354354228797782]},\"seenTime\":\"2016-10-04T15:41:21Z\",\"ssid\":\".interwebs\",\"os\":\"Generic Linux\",\"clientMac\":\"74:da:38:56:0a:80\",\"seenEpoch\":1475595681,\"rssi\":41,\"ipv6\":null,\"manufacturer\":\"Edimax Technology\"},{\"ipv4\":\"/192.168.0.42\",\"location\":{\"lat\":51.53569388164768,\"lng\":-0.06959447280598852,\"unc\":0.146771977623702,\"x\":[3.4765536752229584],\"y\":[4.354354228797782]},\"seenTime\":\"2016-10-04T15:41:21Z\",\"ssid\":\".interwebs\",\"os\":\"Mac OS X\",\"clientMac\":\"f4:5c:89:9b:17:67\",\"seenEpoch\":1475595681,\"rssi\":54,\"ipv6\":null,\"manufacturer\":\"Apple\"},{\"ipv4\":\"/192.168.0.56\",\"location\":{\"lat\":51.53569388164768,\"lng\":-0.06959447280598852,\"unc\":0.5025733754278429,\"x\":[3.4765536752229584],\"y\":[4.354354228797782]},\"seenTime\":\"2016-10-04T15:41:15Z\",\"ssid\":\".interwebs\",\"os\":null,\"clientMac\":\"18:fe:34:d7:7c:26\",\"seenEpoch\":1475595675,\"rssi\":51,\"ipv6\":null,\"manufacturer\":\"Espressif\"},{\"ipv4\":null,\"location\":{\"lat\":51.5357040016257,\"lng\":-0.06965005722969408,\"unc\":32.45974335860272,\"x\":[7.462976981175844],\"y\":[3.983523257388808]},\"seenTime\":\"2016-10-04T15:41:01Z\",\"ssid\":null,\"os\":null,\"clientMac\":\"00:26:ab:b8:a9:a5\",\"seenEpoch\":1475595661,\"rssi\":9,\"ipv6\":null,\"manufacturer\":\"Seiko Epson\"},{\"ipv4\":\"/192.168.0.69\",\"location\":{\"lat\":51.53569388164768,\"lng\":-0.06959447280598852,\"unc\":0.5278878292532063,\"x\":[3.4765536752229584],\"y\":[4.354354228797782]},\"seenTime\":\"2016-10-04T15:41:04Z\",\"ssid\":\".interwebs\",\"os\":null,\"clientMac\":\"18:fe:34:ed:8b:ea\",\"seenEpoch\":1475595664,\"rssi\":60,\"ipv6\":null,\"manufacturer\":\"Espressif\"}]}}"
}
```
８．"supersecret" の部分を「Meraki の設定」で決めた secret へ置き換える
９．「保存」をクリック
１０．「テスト」をクリックして実行する
１１．実験結果に「成功」が表示されることを確認。「詳細」をクリックしてログを開き、"statusCode" が "200", "body" が "CMX POST RECEIVED" となっていることを確認する。
<img width="645" alt="Lambda_verify2.png" src="https://qiita-image-store.s3.amazonaws.com/0/104747/530eae85-4114-113b-bff8-a298ef9de87a.png">

## Meraki の設定（Post URL の追加）
１．Meraki ダッシュボードへアクセス：http://dashboard.meraki.com/
２．"Network-wide" > "CONFIGURE" | "General" をクリック
３．"Location and scanning" 内の "Post URLs" に「AWS Lambda の作成」の手順１０で入手した URL を入力し、"Validate" をクリックする。その結果、Validated https:～ と表示されればOK。これは Meraki から API Gateway へのアクセスが正しく実行されたことを意味する。
４．Save をクリック

この時点から30-120秒程度の間隔で Meraki の Scanning API により API Gateway の URL に対して位置情報の Post が開始される。データは Dynamo DB に保存される。

## DynamoDB での確認
１．AWS マネジメントコンソールにログインして DynamoDB コンソールを開く
２．「テーブル」＞ "cmxdata" ＞「項目」タブを順にクリック
３．Meraki から取得したデータが書き込まれていれば成功。書込まれるデータカラムは Lambda のコードによって決まっている。
<img width="698" alt="dynabodb1.png" src="https://qiita-image-store.s3.amazonaws.com/0/104747/9904b0c4-a86a-23c5-9aee-f68be2b475e7.png">