Raspberry Pi＋PythonでLoRaWAN Deviceにチャレンジしてみる（３）

#今回の内容
[前回](https://qiita.com/nagase/items/a99248d6f13079e8abca)からの続きで、3回目となります。（長くなってスミマセン･･･）
これまでの2回でプラットフォームへデータが送信できることを確認できましたので、今回と次回の2回に渡ってデバイスに取り付けたセンサーで取得したデータを、Amazon S3に保存するところまでやってみたいと思います。

#トポロジ
アプリケーションサーバ側は下図のように、`API Gateway`、`Kinessis Firehose`(以下、Firehose)、`S3`を使って構築します。

![Topology.png](https://qiita-image-store.s3.amazonaws.com/0/94476/30821428-3448-38fa-3422-04b8f11bc409.png)

#AWS側の準備

クラスメソッドさんが解りやすい情報を[公開](https://dev.classmethod.jp/cloud/aws/api-gateway-aws-service-proxy-kinesis-firehose/)していましたので、その情報にある「AWSサービスプロキシ」方式に倣って設定してみます。
尚、クラスメソッドさんが情報を公開した時点では、東京リージョンでのFirehose利用はできませんでしたが、現在は利用できますので、AWS側は全て東京リージョンで構成することにしました。

##Firehose

1. AWSマネジメントコンソールから`Kinesis`を開き、次に`Firehoseコンソールに移動`をクリック
    ![firehose_002.png](https://qiita-image-store.s3.amazonaws.com/0/94476/4596f348-52f0-2087-2719-95156de7aebf.png)

2. `Create Delivery Stream`をクリック
    ![firehose_003.png](https://qiita-image-store.s3.amazonaws.com/0/94476/b48218f6-8c38-389c-b1de-79de961d54ea.png)

3. `Delivery stream name`に任意のストリーム名を入力、`Source`に`Direct PUT or other sources`が選択されていることを確認し、ページ下部の`Next`をクリック
    ![firehose_004.png](https://qiita-image-store.s3.amazonaws.com/0/94476/3d90482e-e943-09a5-b6d3-deae574eda3e.png)

4. `Record transformation`に`Disabled`が選択されていることを確認し、`Next`をクリック
    ![firehose_005.png](https://qiita-image-store.s3.amazonaws.com/0/94476/af9a90ea-3695-a08d-e421-8627e9815000.png)

5. `Destination`に`Amazon S3`を選択、`S3 bucket`は任意のバケット（既存、または`Create new`をクリックして新しいバケットを作成）を選択し、`Next`をクリック
    ![firehose_006.png](https://qiita-image-store.s3.amazonaws.com/0/94476/bfc23348-9a1a-d694-9e16-9d4be62aaece.png)

6. `Buffer interval`を`60`に変更し、`IAM role`にある`Create new, or Choose`ボタンをクリック
    ![firehose_007.png](https://qiita-image-store.s3.amazonaws.com/0/94476/ea76c5ab-6458-3975-4233-84afd21a0cf0.png)

7. 別ページが新たに開くので、`IAMロール`は`新しいIAMロールの作成`のまま、`ロール名`に任意の名称を入力し、`許可`をクリック
    ![firehose_008.png](https://qiita-image-store.s3.amazonaws.com/0/94476/654c0195-4d58-abcc-39c2-6f636dee030c.png)

8. IAMロールページが閉じて元のページに戻るので、`IAM role`に上記で作成したロール名が表示されていることを確認し、`Next`をクリック
    ![firehose_009.png](https://qiita-image-store.s3.amazonaws.com/0/94476/22bfe7e5-fae1-55f2-f65b-4ce020cb24f1.png)

9. ここまでの操作で設定した内容が表示されるので、内容を確認して`Create delivery stream`をクリック
    ![firehose_010.png](https://qiita-image-store.s3.amazonaws.com/0/94476/8a121d7b-c6ad-71a7-19ba-ddae61eef7ec.png)

10. `S3 Delivery Streams`一覧に、作成したストリームが表示される
    ![firehose_011.png](https://qiita-image-store.s3.amazonaws.com/0/94476/70b5cf1e-a0b0-e85b-fe92-bb6364485a5b.png)

以上の手順で、FirehoseがS3を操作するためのIAM Roleと、Firehoseストリーム、S3バケット（新規作成した場合）が作成されます。

またFirehoseストリームに対して、次のようなIAM Role(`lorawan_delivery_role`)が作成されています。

###信頼関係（Trust Relationships）

Firehose に対し、AssumeRoleすることを許可するポリシードキュメント

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "firehose.amazonaws.com"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "<アカウントID>"
        }
      }
    }
  ]
}
```

###アクセス権限（Permissions）

S3やAmazon Lambda、Logs、Kinesisに対して、操作を許可するインラインポリシー

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": [
                "s3:AbortMultipartUpload",
                "s3:GetBucketLocation",
                "s3:GetObject",
                "s3:ListBucket",
                "s3:ListBucketMultipartUploads",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::<バケット名>",
                "arn:aws:s3:::<バケット名>/*",
                "arn:aws:s3:::%FIREHOSE_BUCKET_NAME%",
                "arn:aws:s3:::%FIREHOSE_BUCKET_NAME%/*"
            ]
        },
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction",
                "lambda:GetFunctionConfiguration"
            ],
            "Resource": "arn:aws:lambda:ap-northeast-1:<アカウントID>:function:%FIREHOSE_DEFAULT_FUNCTION%:%FIREHOSE_DEFAULT_VERSION%"
        },
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": [
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:ap-northeast-1:<アカウントID>:log-group:/aws/kinesisfirehose/lorawan:log-stream:*"
            ]
        },
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": [
                "kinesis:DescribeStream",
                "kinesis:GetShardIterator",
                "kinesis:GetRecords"
            ],
            "Resource": "arn:aws:kinesis:ap-northeast-1:<アカウントID>:stream/%FIREHOSE_STREAM_NAME%"
        },
        {
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": [
                "arn:aws:kms:region:accountid:key/%SSE_KEY_ARN%"
            ],
            "Condition": {
                "StringEquals": {
                    "kms:ViaService": "kinesis.%REGION_NAME%.amazonaws.com"
                },
                "StringLike": {
                    "kms:EncryptionContext:aws:kinesis:arn": "arn:aws:kinesis:%REGION_NAME%:<アカウントID>:stream/%FIREHOSE_STREAM_NAME%"
                }
            }
        }
    ]
}
```

##API Gateway向けIAMロール作成

API Gatewayのメソッド作成時に指定するIAMロールを、新たに作成します。
今回は`firehose_for_lorawan`という名前で作成しました。

###信頼関係（Trust Relationships）
API GatewayがAssumeRoleすることを許可するポリシードキュメント

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "apigateway.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

###アクセス権限（Permissions）

Firehoseにデータレコードを書き込むAPI(`PutRecord`、`PutRecordBatch`)を許可するインラインポリシー

<dl>
  <dt>PutRecord</dt>
  <dd>単一のデータレコードをストリームに書き込む</dd>
  <dt>PutRecordBatch</dt>
  <dd>1回の呼出しで複数のデータレコードをストリームに書き込む</dd>
</dl>

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt",
            "Effect": "Allow",
            "Action": [
                "firehose:PutRecord",
                "firehose:PutRecordBatch"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
```

##API Gateway作成

1. AWSマネジメントコンソールから`API Gateway`を開き、`APIの作成`をクリック、更に`新しいAPI`を選択し、`API名`に任意の名称を入力して`APIの作成`をクリック
    ![apigw_001.png](https://qiita-image-store.s3.amazonaws.com/0/94476/de5fe37c-eef8-b2d0-8461-8e2147c00efc.png)

2. `アクション`をクリックし、`リソースの作成`をクリック
    ![apigw_002.png](https://qiita-image-store.s3.amazonaws.com/0/94476/e873788b-efa3-e2b8-7ea0-1afaa9fba685.png)

3. リソース名に`api`を入力し、`リソースの作成`をクリック（`リソースパス`は、デフォルトでリソース名と同一文字が自動入力される）
    ![apigw_003.png](https://qiita-image-store.s3.amazonaws.com/0/94476/b2f78138-3313-d0ae-2b54-5258f6051058.png)

4. 上記2～3と同様に、リソース`api`の下にリソース`v1`を作成
    ![apigw_004.png](https://qiita-image-store.s3.amazonaws.com/0/94476/8fef61e7-650e-c072-7e71-61e9bb3409ea.png)

5. `アクション`をクリックし、`メソッドの作成`をクリック
    ![apigw_005.png](https://qiita-image-store.s3.amazonaws.com/0/94476/d5bb6f33-720f-3540-84d8-2c661490573a.png)

6. ドロップダウンから`POST`を選択し、確定アイコンをクリック
    ![apigw_006.png](https://qiita-image-store.s3.amazonaws.com/0/94476/31bc7908-3bd8-39ff-ce34-cbe535b8127b.png)

7. 下図に従って必要な値を入力し、`保存`をクリック
    ![apigw_007.png](https://qiita-image-store.s3.amazonaws.com/0/94476/58960230-7ebe-8fef-3bb4-ff908729c83a.png)
    >
    > |項目|設定値|
    > |:--|:--|
    > |統合タイプ|AWSサービス|
    > |AWSリージョン|Firehoseを作成したリージョン　※今回は東京リージョンなので`ap-northeast-1`|
    > |AWSサービス|Firehose|
    > |HTTPメソッド|POST|
    > |アクション|PutRecord|
    > |実行ロール|API Gateway向けIAMロール（`firehose_for_lorawan`）|

8. `メソッドの実行`ページに遷移するので、`統合リクエスト`をクリック
    ![apigw_008.png](https://qiita-image-store.s3.amazonaws.com/0/94476/aecb3da6-85da-c1d8-038a-6a9c7cadce11.png)

9. `本文マッピングテンプレート`をクリックして展開し、`リクエストが定義されていない場合`を選択、更に`マッピングテンプレートの追加`をクリックし、`Content-Type`に`application/json`と入力して確定アイコンをクリック
    ![apigw_009.png](https://qiita-image-store.s3.amazonaws.com/0/94476/74c7c89b-b20e-a797-e837-d43ee007135f.png)

10. `テンプレートの生成`欄が表示されるので、以下のコードを入力し、`保存`ボタンをクリック

    ```json
    {
      "DeliveryStreamName": "lorawan_stream",
      "Record": {
        "Data": "$util.base64Encode($input.json('$.DevEUI_uplink'))"
      }
    }
    ```
    ![apigw_010.png](https://qiita-image-store.s3.amazonaws.com/0/94476/3db5891a-cf17-e379-7705-87d667e858f1.png)

    **[補足] マッピングテンプレートについて**
    [公式ドキュメント](http://docs.aws.amazon.com/ja_jp/firehose/latest/APIReference/API_PutRecord.html)を確認すると、Firehoseに単一のデータレコードを書き込む`PutRecord` APIは、次のフォーマットになります。

    ```
    {
       "DeliveryStreamName": "string",
       "Record": { 
          "Data": blob
       }
    }
    ```

    また、LoRaWANプラットフォーム（ThingPark）からAPI GatewayにPOST送信されるJSONオブジェクトは、次のようなフォーマットです。

    ```
    {
        "DevEUI_uplink": {
            "Time": "2017-11-02T08:48:47.595+01:00",
            "DevEUI": "47XXXXXXXXXXXXXX",
            "FPort": "8",
            "FCntUp": "35",
            "ADRbit": "1",
            "MType": "4",
            "FCntDn": "40",
            "payload_hex": "1b1083202fd06b8c5410350c16b5",
            "mic_hex": "41293adb",
            "Lrcid": "01234567",
            "LrrRSSI": "-116.000000",
            "LrrSNR": "-5.000000",
            "SpFact": "9",
            "SubBand": "G0",
            "Channel": "LC4",
            "DevLrrCnt": "1",
            "Lrrid": "65XXXXXX",
            "Late": "0",
            "LrrLAT": "33.588463",
            "LrrLON": "130.397446",
                "Lrrs": {
                "Lrr": [
                    {
                        "Lrrid": "65XXXXXX",
                        "Chain": "0",
                        "LrrRSSI": "-116.000000",
                        "LrrSNR": "-5.000000",
                        "LrrESP": "-122.193314"
                    }
                ]
            },
            "CustomerID": "100008500",
            "CustomerData": {
                "alr": {
                    "pro": "LORA/Generic",
                    "ver": "1"
                }
            },
            "ModelCfg": "0",
            "DevAddr": "01XXXXXX"
        }
    }
    ```

    つまり、プラットフォームから送られてくるJSONオブジェクトの最上位メンバー `DevEUI_uplink`を、PutRecord APIの`Data`メンバーにマッピングするすることで、`DevEUI_uplink`メンバー内のデータを、Firehoseストリームに書き込む仕組みとなっています。
    更に、PutRecord APIの`Data`メンバーの値（上記APIフォーマットの`blob`）は、Base64でエンコードする必要があるため、組み込み関数`base64Encode()`を利用しています。
    マッピングテンプレートのその他組み込み関数については、[こちら](http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html)に詳しく説明があります。

ここまでの手順で、AWS側の設定作業は完了です。

##API Gatewayテスト

以下の手順で、API Gatewayのテストを行ってみます。
（背後でFirehoseストリームと連動していますので、実質AWS側の統合テストになります）

1. ページ上部にスクロールし、`メソッドの実行`をクリック
    ![apigw_011.png](https://qiita-image-store.s3.amazonaws.com/0/94476/8f9e724e-3991-4881-fed0-e0c2797e1c25.png)

2. `テスト`をクリック
    ![apigw_012.png](https://qiita-image-store.s3.amazonaws.com/0/94476/e3727d1e-ad99-3c55-4dc7-9579ffce112d.png)

3. `リクエスト本文`に以下のJSONコードを入力し、`テスト`をクリック

    ```json
    {
        "DevEUI_uplink" : "Fukuoka City LoRaWAN"
    }
    ```
    ![apigw_013.png](https://qiita-image-store.s3.amazonaws.com/0/94476/b0f6fe85-ce81-b5b8-4ea3-cbae15ab1437.png)

4. （レスポンスの）`ステータス`が`200`になっているか確認
    ※ステータスが`200`でない場合、表示されているログからエラー内容を確認できます。
    ![apigw_014.png](https://qiita-image-store.s3.amazonaws.com/0/94476/0e9ba1cc-6ab5-7890-7f9b-75e7f610ca6e.png)

5. S3バケットを確認し、データを確認
    ![apigw_015.png](https://qiita-image-store.s3.amazonaws.com/0/94476/087818f7-489a-73a0-ee4d-d5bb2faed538.png)
    バケットに保存されたデータの中身

    ```
    "Fukuoka City LoRaWAN"
    ```

##API Gatewayデプロイ

以下の手順で、作成したAPIをユーザーが呼び出せるようにデプロイします。

1. `アクション`をクリックし、`APIのデプロイ`をクリック
    ![apigw_016.png](https://qiita-image-store.s3.amazonaws.com/0/94476/4ee2607f-18c7-399d-f0d5-c928740a7d21.png)

2. `デプロイされるステージ`に`新しいステージ`を選択、ステージ名に任意の名称を入力（今回は`prod`）して`デプロイ`をクリック
    ![apigw_017.png](https://qiita-image-store.s3.amazonaws.com/0/94476/b63f61ed-fcd8-dbab-7116-eb0e0ab2cf71.png)

3. ステージエディタが表示されればデプロイ完了
    尚、`URLの呼び出し`に記載されたURLに続けてリソースパスを指定したアドレス(今回の場合、`https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/api/vi`)が、プラットフォーム側で設定するアプリケーションサーバのアドレスになります。（=>前回の[アプリケーションサーバの登録](https://qiita.com/nagase/items/a99248d6f13079e8abca#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AE%E7%99%BB%E9%8C%B2)を参照）
    ![apigw_018.png](https://qiita-image-store.s3.amazonaws.com/0/94476/417b8d4f-3b87-da05-0613-31d76e8b021a.png)

#次回
LoRaWANデバイスにセンサーを取り付け、送信してみます。