Raspi(デバイス)とAWSIoT(クラウド)間でMQTTを使ってメッセージを送受信する

# はじめに

AWSのIoTのサービスといえば**AWSIoT**ですよね！
つい先日大阪で行われてた「AWS Cloud Roadshow 2017」の中で、**AWS Greengrass**が東京リージョンに上陸したことが発表されました！
ということでですね、デバイスとクラウド間のやり取りの基本的な部分をAWSIoTを利用して実装していこうと思います

※前半部分には[sakura.io ✕ AWS IoTハンズオン [AWS編]](https://qiita.com/Ichiro_Tsuji/items/3815f2b683336e7088f4)の内容を投稿者の方の許可をいただいた上で、使わせていただいております

#**[0] 主な流れ**

AWSIoTとラズパイ間の通信をMQTTでつなぎ、送受信する手順を紹介します
おおまかな流れはこんな感じです

1. AWSIoTで**証明書**を発行する
2. 発行した証明書を用いてデバイス(Raspi)とAWSIoTを接続する
3. メッセージを送受信する
4. (おまけ)Lambda経由でDynamoDBにデータをPUTする

#**[1] AWS IoTの設定(Thing)**

AWS IoTは概念が少々複雑ですが、ある程度把握しておかないとこれ以降の設定内容が理解できないと思います。[AWS Black Belt Online Seminar 2016 AWS IoT](https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-online-seminar-2016-aws-iot)で学習すると良いでしょう。

1. **証明書**に**Thing**と**ポリシー**を紐付ける
2. 証明書をデバイスにインストールする
3. 流れてきたトピックに対するアクション(連携先等)を**ルール**で定義する

##ポリシーの作成

### AWS IoTコンソールを開く

![スクリーンショット 2017-09-18 00.46.54.png](https://qiita-image-store.s3.amazonaws.com/0/80392/6b1d3201-444a-423f-6818-b07d6873b119.png)

### リージョンが「Asia Pacific (Tokyo)」でない場合は変更する

![4bd6b0f2-3593-3b10-1839-62f3c2f13173.png](https://qiita-image-store.s3.amazonaws.com/0/80392/1c4f7929-0ab8-557d-630b-3d2479e0dc95.png)

### [Get started]をクリックする(下図は表示されない場合もある)

![スクリーンショット 2017-09-11 17.23.40.png](https://qiita-image-store.s3.amazonaws.com/0/80392/1ab2e28c-7ef6-32a7-ad17-a7f7a858eece.png)

### ポリシーを作成する

![スクリーンショット 2017-09-18 01.26.53.png](https://qiita-image-store.s3.amazonaws.com/0/80392/ce2830c3-fb30-bbf4-72fa-0eb00df9f29e.png)

### 下表の通り入力して[Create]をクリックする

| 項目 | 入力内容 |
|---|---|
| Name | AllPubSub |
| Action | iot:* |
| Resource ARN | * |
| Effect | Allow |

![スクリーンショット 2017-09-18 01.12.18.png](https://qiita-image-store.s3.amazonaws.com/0/80392/ec9efcde-1265-ddb6-d0a7-6ef5d9e31ecf.png)

##Thingと証明書の作成

### Thingを作成する

![スクリーンショット 2017-09-18 00.49.51.png](https://qiita-image-store.s3.amazonaws.com/0/80392/e1951359-33a5-efa3-5c04-0d35f7505921.png)

### **device01**を入力して[Create thing]をクリックする

![awsiot_test_6.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/37e8a125-2cd9-595e-14b4-9fe42ab05c54.png)

### 作成したThingをクリックする

![awsiot_test_7.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/9a05324c-331f-63eb-f067-1c988509e939.png)

### 証明書を作成する

![スクリーンショット 2017-09-18 00.55.14.png](https://qiita-image-store.s3.amazonaws.com/0/80392/29782f80-18dc-ddbd-37e2-064f3c18be5c.png)

### クライアント証明書／クライアント秘密鍵／ルート証明書の3種類をダウンロードする(ルート証明書のみ、右クリック -> リンク先を別名で保存)<BR><BR> **<font color="Red">[Activate]</font>**(押し忘れ注意！) -> [Attach a policy]の順にクリックする

![スクリーンショット 2017-09-18 00.57.16.png](https://qiita-image-store.s3.amazonaws.com/0/80392/69bc4680-8f44-98b8-d67c-d4ebbe8ce6b7.png)

### [AllPubSub]を選択して[Done]をクリックする

![スクリーンショット 2017-09-18 01.18.49.png](https://qiita-image-store.s3.amazonaws.com/0/80392/f8dd9a0f-20cb-9798-114e-9d573303f836.png)

### 戻る

![スクリーンショット 2017-09-18 01.24.11.png](https://qiita-image-store.s3.amazonaws.com/0/80392/9b5d12bf-a7d1-8c74-3673-aa4dc08fb70b.png)

##エンドポイントを確認する

### 歯車マークをクリックするとエンドポイントが表示されるので控えておく

![スクリーンショット 2017-09-18 01.38.48.png](https://qiita-image-store.s3.amazonaws.com/0/80392/aa0c4723-d822-5827-061a-9a4c3bc98557.png)

#**[2] RaspiとAWSIoTを接続する**

前章で発行した証明書を使って、RaspiとAWSIoTを接続します

使用したRaspiの情報は以下のとおり

* Raspbian GNU/Linux 8.0 (jessie)
* Linux raspberrypi 4.4.34

## MQTTの実装

MQTTの実装には「mosquitto」を利用します。下記コマンドを実行することでAWSIoTとの通信を行います

* Pub mosquitto_pubコマンドで光量データを送信 
* Sub mosquitto_subコマンドで光量データを受信

### mosquittoのインストール

Raspberry Piのデフォルトリポジトリのmosquitto-clientsはバージョンが古く、TLSのエラーが発生するため最新のmosquitto-clientsをインストールします

```
$ wget  http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key
$ sudo apt-key add mosquitto-repo.gpg.key
$ wget http://repo.mosquitto.org/debian/mosquitto-stretch.list
$ sudo cp mosquitto-stretch.list /etc/apt/sources.list.d/
 
$ sudo apt-get update
$ sudo reboot

$ sudo apt-get install  mosquitto-clients
$ mosquitto_sub --help |grep version
 mosquitto_sub version 1.5 running on libmosquitto 1.5.0.
```

2018/05/28時点で最新のバージョンは1.5.0です

先ほど発行した証明書(クライアント証明書／クライアント秘密鍵／ルート証明書の3種類)をRasberry Piにコピーします
※この際コピーミスを防ぐために、SFTPなどで**確実に**コピーするようにしてください。scpでホームディレクトリにファイルを置く例

```
$ scp file-name pi@IP_ADDRESS:~
```

## AWSIoT MQTTクライアントを開く

### [Test]を選択する

![awsiot_menu.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/82b0ef3a-1489-2167-d4d2-1e0ba35f4ed1.png)

### [Subscription topic]に「$aws/things/device01/test」と入力する

![awsiot_test_1.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/c465f66a-ac9a-641f-edee-648cbf67a38c.png)

### [Subscribe to topic]をクリックする

![awsiot_test_2.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/8d42b8d5-29f7-e2bd-80ec-9da9f62e8652.png)

下記のような画面になると思います

![awsiot_test_3.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/c5042ee5-add7-e416-c708-48a25cf456ce.png)


### ラズパイからAWSIoTへのメッセージ送信(publish)

例)トピック「$aws/things/device01/test」に対して文字列「test message」を送信します

```
$ mosquitto_pub --cafile [ルート証明書] --cert [クライアント証明書] --key [クライアント秘密鍵] -h [AWSIoTのエンドポイント] -p 8883 -q 1 -t '$aws/things/device01/test' -i device01 --tls-version tlsv1.2 -m '{"test": "message"}' -d
```

例)トピック「$aws/things/device01/test」に対してJSONファイル「data_sample.json」を送信します

```json:data_sample.json
{
  "data": [
    {
      "d1": "0000",
      "d2": "9999",
      "d3": "0003",
      "d4": "0004",
      "d5": "0005",
      "d6": "0006",
      "d7": "0007",
      "d8": "0008",
      "d9": "0009",
      "d10": "0010",
      "serial": "00000001",
      "time": "201706010100"
    }
  ]
}
```

```
$ mosquitto_pub --cafile [ルート証明書] --cert [クライアント証明書] --key [クライアント秘密鍵] -h [AWSIoTのエンドポイント] -p 8883 -q 1 -t '$aws/things/device01/test' -i device01 --tls-version tlsv1.2 -f data_sample.json -d
```

問題なく送信が完了すれば、先ほど開いたテスト画面にデバイスからのメッセージが表示されます
↓こんな感じ(data_sample.jsonを送信した際の結果)

![awsiot_test_5.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/598f43d0-c492-b2ca-be0b-bed09893d572.png)


### AWSIoTからのメッセージ受信(subscribe)

例)トピック「$aws/things/device01/test」に対するメッセージを待ち受けます

```
$ mosquitto_sub --cafile [ルート証明書] --cert [クライアント証明書] --key [クライアント秘密鍵] -h [AWSIoTのエンドポイント] -p 8883 -q 1 -t '$aws/things/device01/test' -i device01 --tls-version tlsv1.2
```

上記コマンドでsubscribeしている状態で、テスト画面からメッセージをデバイスへ送信します

![awsiot_test_4.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/deb84668-6a89-baaa-831c-b09560944701.png)

問題なければデバイス側のターミナルに、こんなメッセージが表示されます

```
{
  "message": "Hello from AWS IoT console"
}
```

### Device shadowを更新する

AWSIoTにはすでに予約されているトピックがあり「$aws/things/device01/shadow/update」に対してメッセージを送信するとshadowを更新することができます

```json:shadow_sample.json
{
  "state":
    {
      "desired":
        {
          "status_data": [
            {
              "status1": "1101",
              "status2": "1202",
              "status3": "1303",
              "status4": "1404",
              "status5": "1005",
              "status6": "1006"
            }
          ]
        }
    }
}
```

```
$ mosquitto_pub --cafile [ルート証明書] --cert [クライアント証明書] --key [クライアント秘密鍵] -h [AWSIoTのエンドポイント] -p 8883 -q 1 -t '$aws/things/device01/shadow/update' -i device01 --tls-version tlsv1.2 -f shadow_sample.json -d
```

#**[3]Lambda経由でDynamoDBにデータをPUTする(おまけ)**

前章でデバイスからAWSIoTにメッセージを送信することができるようになりましたので、デバイスからJSONのデータを送信し、それをDynamoDBに格納してみます

簡単な流れとしては
1. AWSIoTのルールを設定し、特定のトピックにメッセージが送信されたらLambdaをキックする
2. Lambdaが受け取ったデータを加工しDynamoDBにPUTする

送信するデータは前章で作成した「data_sample.json」を利用します

## Lambdaの作成

関数名「IoT-Lambda-DynamoDB」というLambda関数を作成していきます
今回はRuntimeをNode.js6.10でLambdaを作成しています

### Policy

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": "dynamodb:PutItem",
            "Resource": "arn:aws:dynamodb:ap-northeast-1:xxxxxxxxxx:table/iot-data-lambda"
        }
    ]
}
```

※DynamoDBにPUTする部分のARNはご利用されているAWSのアカウントIDに書き換えてください

### Code(Node.js)

```js
const AWS = require("aws-sdk");
const dynamoDB = new AWS.DynamoDB.DocumentClient({
    region: "ap-northeast-1"
});

exports.handler = (event, context, callback) => {
    const item = event["data"][0];
    item["deviceID"] = event["deviceID"];
    const nowTime = Date.now();
    item["time"] = String(nowTime);
    
    const params = {
        TableName: "iot-data-lambda",
        Item: item
    };
    
    dynamoDB.put(params).promise();
};
```

## DynamoDBの作成

LambdaがPUTするDynamoDBを作成します

### DynamoDBの情報

| 項目 | 入力内容 |
|---|---|
| TableName | iot-data-lambda |
| Primary partition key | deviceID (String) |
| Primary sort key | time (String) | 


## AWSIoTルールの作成

1. AWSIoTコンソール画面左メニューからルール画面に移動し、画面右上の「create」をクリックしてルール作成画面に移動する
2. 「Name」に任意の文字列を入力する
3. 「Attribute」「Topic filter」に下記の内容を入力する

| 項目 | 入力内容 |
|---|---|
| Attribute | *,topic(3) AS deviceID |
| Topic filter | $aws/things/+/data |

「topic(3) AS deviceID」と書いておくことで、トピックの3番目をdeviceIDとしてデータの中に含ませて次のActionに渡します
今回の場合はトピックの「+」に入る文字列が、Lambdaに渡されるeventの中にdeviceIDが含まれるようになります

![awsiot_test_8.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/76039b00-d5bf-60f5-0f44-f49cbb0717d1.png)<br>
4. [Set one or more actions]の「Add action」をクリックし、Lambdaを選択、画面下部の[Configure action]をクリック

![awsiot_test_9.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/3273ff79-4f9d-56fa-c6b5-ea35a63cb73f.png)

![awsiot_test_10.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/6e280994-b172-e386-d634-8fad6b9abd3b.png)<br>
5. 先ほど作成したLambda関数を選択し[Add action]をクリック

![awsiot_test_11.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/d3c2d6e4-ffe5-53d8-c0a3-36c422e97ec9.png)<br>
6. 最後に[Create rule]をクリック

これで「IoT_Lambda_DynamoDB」というルールが作成されました
デバイスからトピック「$aws/things/+/data」(+にはdeviceIDとなる文字列を入れる)にメッセージが送信されたときに、Lambdaにデータが渡され、加工してDynamoDBに格納します

## 実際に実行してみる

デバイスからデータを送信する

```
$ mosquitto_pub --cafile [ルート証明書] --cert [クライアント証明書] --key [クライアント秘密鍵] -h [AWSIoTのエンドポイント] -p 8883 -q 1 -t '$aws/things/device01/data' -i device01 --tls-version tlsv1.2 -f data_sample.json -d
```

問題なく処理が実行されればDynamoDBにデータがPUTされる

![awsiot_test_12.PNG](https://qiita-image-store.s3.amazonaws.com/0/95875/3ef9c7c2-0aa0-a5da-3086-77b41ed8c2aa.png)

# さいごに

AWSIoTで発行した証明書ベースでメッセージの送受信ができるようになりました
今回は証明書の発行、デバイスへの証明書のコピーなどすべて手作業で行いましたが、デバイスが複数台になってくるとこんなことしれいられないので、このあたりを自動化するのが必要になってくるなと実感しましたね
そこらへんの自動化を実現してくれたすばらしいお方から資料をいただいたので、試していこうと思います
もし許可をいただいたら記事にまとめようと思いますー

ではまた！