LoRaWANとSORACOMFunnelのAWSIoTアダプタを使ってDynamoDBにデータを書き込む

# はじめに

SORACOMFunnelがAWSIoTに対応しましたね！
ちょうど仕事の関係でSORACOMのシールドが届いたし、近くにLoRaWANのPublicGWもあることだし･･･
ということでちょいと触ってみやした

筆者は今まで、主にKinesisアダプターを利用してデータの収集を行っています
簡単にまとめると、
1. KinesisStreamにセンサーからデータを投げて
2. Lambdaをキックして
3. DynamoDBに突っ込む
といった構成です

また普段からAWSは使っているのですが、AWSIoTを使ってみたことがほとんどなかったので、勉強がてらAWSIoTアダプターでデータの収集をしてみた

## やりたいこと

1. LoRaデバイスからLoRaゲートウェイを通ってFunnel AWSIoTアダプターを利用してAWSIoTにセンサーデータを投げる
2. AWSIoTが受け取ったデータの中にHEX形式でセンサーデータが格納されているので、デコードするためのLambdaファンクションをキックする
3. Lambdaでデータをデコードして必要なデータをJSON形式にまとめて、DynamoDBにPUTする

## SORACOM Funnelって？

```
SORACOM Funnel(以下、Funnel) は、デバイスからのデータを特定のクラウドサービスに直接転送するクラウドリソースアダプターです。
Funnel でサポートされるクラウドサービスと、そのサービスの接続先のリソースを指定するだけで、データを指定のリソースにインプット
することができます。
```
※http://soracom.jp/services/funnel/より抜粋
要するに、デバイスからAWSなどのクラウド上に閉域網でデータを送信することができるサービス(合ってるかな･･･)

## AWSIoTって？

```
AWS IoT によって、さまざまなデバイスを AWS の各種 Services や他のデバイスに接続し、データと通信を保護し、
デバイスデータに対する処理やアクションを実行することが可能になります。
アプリケーションからは、デバイスがオフラインの状態でもデバイスとのやり取りが可能です。
```
※https://aws.amazon.com/jp/iot-platform/how-it-works/より抜粋
うーん、なるほどわからん。とりあえず使ってみよう

## デバイス側の設定

同じ部署の電気系強いお方が気づいたらセッティングしていただいていましたので割愛
この時点でSORACOM Harvestにてデータが送信されているのを確認できている状態

## AWSIoTの設定

Funnelでデータを送信する先のAWSIoTを作成します

### エンドポイントを控える

Funnelを設定する際に必要なAWSIoTのエンドポイントを控えておきます

![AWSIoT_TOP.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/03bb5b4e-10f6-55bc-e73b-797232122ef0.png)

### Ruleを作成する

左のサイドメニューから「Rule」を選択し、「Create a rule」をクリック

![AWSIoT_Rule.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/b63e7932-b636-8fa6-4828-eeb64a28ab9a.png)


「Name」と「Description」を入力する(Descriptionは任意)

![AWSIoT_Rule_name.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/8f7429fb-136a-920f-60be-8f7c2487b5bb.png)


「Attribute」に「*」、「Topic filter」に「IoTDemo/#」を入力
AWSIoTはエンドポイントいかにTopic(今回では「IoTDemo/#」)を指定してリクエストを送ることで、それと一致するTopicFilterを持つRuleが呼ばれます
「Using SQL version」は「2016-03-23」で問題なければそのままでOK

![AWSIoT_Rule_massage.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/7924962d-0787-111e-83ce-f1bc13be9fb2.png)

「Set one or more actions」の「add action」をクリック

![AWSIoT_Rule_set_action.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/9dd0edb2-44ea-48e6-35bc-82d702a20aa7.png)

今回はLambdaでデコードする必要があるため「Invoke a Lambda function passing the message data」を選択

![AWSIoT_Rule_select_lambda.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/2d5d38b7-01f4-f900-560a-1fb7698c74fa.png)

「Configure action」を選択

![AWSIoT_Rule_select_lambda_button.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/8ce0eea4-9ac4-1cb9-d0b1-dd6e28f920d1.png)

キックするLambda Functionを選択
今回は初めて作成するので、Lambdaが呼ばれたときのeventの中身をログに吐き出すLambdaを作成して、それをキックするようにします
※DynamoDBに格納する処理は後ほど実装

「Create a new resouce」をクリック。Lambdaのページに遷移します

![AWSIoT_Rule_lambda_create.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/fd4037de-a043-d719-5df9-eef4ab1b507b.png)

「Blank Function」を選択

![Lambda_create.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/def5e13d-dab3-f187-17e9-fe063b5db0fd.png)

Lambdaのトリガーを設定
「IoTタイプ」は「カスタムIoTルール」を選択
「ルール名は」現在作成中のルール名
「SQLステートメント」は作成中の「Rule query statement」の中身をコピー
「次へ」をクリック

![Lambda_trigger.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/c738e87c-8afd-9bf2-63b6-80369c3ce1e4.png)

「名前」はお好きなFunction名をつけてください
「ランタイム」は筆者の好みによりNode.jsです
コードには

```js
exports.handler = (event, context, callback) => {
    console.log(event);
};
```
と書いておいてください。

![Lambda_setting.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/115b73f3-ee01-8aaa-0b22-c730ae11b77f.png)


あとは、DynamoDBの権限を持ったロールを選択(作成)して、ページ下部の「次へ」をクリックしてLambdaFunctionを作成してください

AWSIoTのページに戻って、先ほど作成したLambdaFunctionを選択し、「Add action」をクリック

![AWSIoT_Rule_add_lambda.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/6acb5804-1418-41f5-7598-0f31532ac884.png)

その後「create Rule」をクリックするとRuleが作成されます
これでAWSIoTのRule作成が完了です

## SORACOM Funnelの設定

まず、SORACOMコンソールにログインし、再度メニューから「LoRaグループ」⇒「追加」をクリックします
ポップアップが出てきてグループ名を入力するように言ってくるので、任意のグループ名を入力しグループを作成します

作成したグループを選択し、設定画面に移動します

転送先サービス：AWS IoT
転送先URL：https://<AWSIoTのエンドポイント>/rule内で作成したSQLTopicFilter/#{deviceId}
認証情報：AWSIoTの権限を持ったIAMアカウント情報で作成したもの
送信データ形式：無難にJSON

![funnel_setting.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/d49f52c1-5af3-72f2-9d64-10be78ec0fdf.png)

※転送先URLにはプレースホルダーを作成することができます
　　- SIMを利用する場合：{imsi}
　　- LoRaデバイスを利用する場合：{deviceId}

これでFunnelの設定は完了です

## Lambdaの実装

デバイスの電源を入れ、データが送信されるようになると、Lambdaが起動してeventの中身をログに吐き出していると思います
↓こんな感じ

```
2017-06-23T04:13:59.850Z 62014535-57ca-11e7-b4e4-9fbd147f2037 { 
  operatorId: '0123456789',
  timestamp: 1498191237793,
  destination: { 
    resourceUrl: 'https://xxxxxxxxx.iot.ap-northeast-1.amazonaws.com/xxxxxxx/#{deviceId}',
    service: 'aws-iot',
    provider: 'aws' 
  },
  credentialsId: 'iot-sys',
  payloads: { 
    date: '2017-06-23T04:13:54.276320',
    gatewayData: [ [Object] ],
    data: '7b2268223a36312e367d',
    deveui: '1234567890' 
  },
  sourceProtocol: 'lora',
  deviceId: '1234567890' 
}

```

センサーから送られてくるデータはevent["payloads"]["data"]にHEX形式で格納されているので、取り出してデコードする必要があります。

```js

const data = event["payloads"]["data"];
const decodeData = new Buffer(data, "hex").toString("utf8");
```

デコードすると「7b2268223a36312e367d」⇒「{"h": 61.6}」のようなString型になります(これは一例)

Object型のほうが使い勝手がよいので、parseしてしまいましょう

```js

const parseData = JSON.parse(decodeData); // {h : 61.6}
```

あとはDynamoDBにputで投げつけます

```js:index.js
"use strict";

const AWS = require("aws-sdk");
const co = require("co");
const moment = require("moment-timezone");

const dynamodb = new AWS.DynamoDB.DocumentClient({
  region: "ap-northeast-1"
});

const dynamoPutData = require("./lib/dynamo_put_data");

exports.handler = (event, context, callback) => {
  // UTCなのでJSTに変換
  const date = event["payloads"]["date"];
  const time = moment(date).tz("Asia/Tokyo").format();
  // HEX形式をデコード
  const data = event["payloads"]["data"];
  const decodeData = new Buffer(data, "hex").toString("utf8");
  // Object型に変換
  const parseData = JSON.parse(decodeData);
  // deviceIdを取得
  const deviceId = event["deviceId"];

  // DynamoDBにPUTするItem
  const item = [{
    deviceId: deviceId,
    time: time,
    value: parseData
  }];

  co(function *() {
    yield dynamoPutData.putDynamoDB(dynamodb, item[0]);
  }).then(() => {
    console.log("success!")
  }).catch((err) => {
    console.log(err);
  });
};

```

```js:dynamo_put_data.js
"use strict";

class dynamoPutData {
  /**
   * DynamoDBへのPUT処理
   * @param {DocumentClient} dynamoDB
   * @param item
   * @returns {Promise}
   */
  static putDynamoDB(dynamoDB, item) {
    const params = {
      TableName: "TABLE_NAME",
      Item: item
    };
    return dynamoDB.put(params).promise();
  }
}

module.exports = dynamoPutData;
```

dynamo_put_data.js中の"TABLE_NAME"にはデータを投げつけるテーブル名を書いてください
関数を外だしして複数ファイルがあるので、Lambdaにはソースコード一式をZIPに固めてアップする方法でデプロイを行います
データが送られてきてLambdaがキックされると、DynamoDBにデータが格納されていると思います

## まとめ

日ごろからAWSのサービスを使っていましたが、AWSIoTを利用する機会がなくとてもいい経験になりました。
今回はデバイスからクラウドといった方向でしたが、AWSIoTを利用すればその逆方向も実現することができるらしいので、近々そういった実装もしてみたと思います

では！