AWS IoTハンズオンでIoTアプリケーション作成

## はじめに
今回はAWSのAWS IoT公式ハンズオンに沿ってIoTアプリケーションを作成してみました。ハンズオンで作成したアプリや環境などは、課金が発生しないように基本的にはクリーンアップしてしまうことが多いので、自分の備忘録としても記事として残しておきたいと思います。また、記載しながら理解した内容の整理などもできればよいなと思って記載してます。

## 参考サイト
* [AWS IoT Core 初級ハンズオン](!https://catalog.us-east-1.prod.workshops.aws/workshops/b3e0b830-79b8-4c1d-8a4c-e10406600035/ja-JP)
本記事で利用している画像などは上記サイトから引用しています。

## やってみる
では実際にハンズオンを開始していきます。ただ、ハンズオンを開始する前にAWS IoTについて基礎知識がない場合は[こちらのブラックベルト](!https://youtu.be/vs9i_yOhYCA)を見ておくとかなりハンズオンの中身など理解度が深まると思います。

## 構築アーキテクチャ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/d4a46310-6ade-9925-a623-0e77dacb46f0.png)
アーキテクチャのポイントとしては以下のような点があると思います。
* 今回はIoTのdummy deviceとしてCloud9を利用する。
* IoTデバイス<>AWS IoT Coreの連携だけでなく、AWS IoT Coreと各AWSサービスの連携も行っており、より実運用に近いイメージでハンズオンが実施できる。
* AWS IoT Coreに連携されたデータを後続の各AWSサービス（AWS Lambda,Amazon S3）に連携する際にAmazon Kinesisサービスを用いてデータフローをより最適化している。

IoTデバイスとAWS IoT Coreが連携されたあと、より具体的には以下３つの機能を他AWSサービスと連携することで実現しています。
* Webアプリケーション（一番上の連携）
* ニアリアルタイムなデータの可視化（真ん中の連携）
* データレイク構築（一番下の連携）

## IoTデバイス（Cloud9）のセットアップ
以下のようにCloud9を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/117e0ed7-11df-77ae-7ee9-ddca7d7c57f1.png)
Cloud9では以下のコマンドを順次実行します。
AWS IoT Device SDK Python v2をインストール
```
pip3 install --user awsiotsdk
```
必要なフォルダの作成と移動
```
mkdir -p ~/environment/dummy_client/certs/
cd ~/environment/dummy_client/
```
ダミーデバイス用のソースコードをダウンロード
```
wget https://awsj-iot-handson.s3-ap-northeast-1.amazonaws.com/aws-iot-core-workshop/dummy_client/device_main.py -O device_main.py
```
ちなみにこちらのダミーデバイス用のコード`device_main.py`では簡単には以下のような処理をしています。
* MQTTコネクション（IoTデバイスとAWS IoT Coreの間の通信プロトコル）を確立
* AWS IoT Coreへのダミーデータの送信
* AWS IoT Core側からのデバイス設定の変更を受け付ける

## AWS IoT Coreのセットアップ
次にダミーデバイスと接続するAWS IoT Core側のセットアップです。

### AWS IoTのエンドポイント確認
各セットアップの前に、ダミーデバイスとの接続の際に後々使うIoT Core側のエンドポイントを確認しておきます。`AWS IoT`>>`Settings`から参照できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/6193a124-b2b9-7642-fde7-b6c24fe410f3.png)

### AWS IoTのログ設定
次にAWS IoTのログの設定を行います。INFOレベル以上のログがCloudWatchに配信されるように設定します。ロールは新規に作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/6fcb164b-b522-58e0-7624-41d8e32c69a7.png)

### IoTポリシーの作成・設定
AWS IoT Core側にアタッチするポリシーを作成・設定します。`AWS IoT`>>`Security`>>`Policies`から設定します。今回はハンズオン用なので、すべてのアクションを許可するポリシーを作成しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/2fc58ddd-2c34-e59b-7b7d-c3f7005646be.png)

### Things(モノ)の作成
この`モノ`と呼ばれる部分ですが、[AWS公式サイト](!https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-thing-management.html)では以下のように説明されています。
>"モノ" とは、特定のデバイスまたは論理エンティティを表します。物理的なデバイスやセンサー (電球や壁のスイッチなど) は、モノとして扱うことができます。

AWS IoT Core側から各デバイスなどを管理する際はこの`モノ`という概念で管理していきます。ちなみに英語では`Things`ですが、IoTを日本語では`モノのインターネット`と訳すことが多いことからAWSでは`Things`を`モノ`と呼んでいるんだと思います。
モノの名前を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/83eea163-3b9d-4c89-7d7d-89afd5074953.png)
次に`デバイスシャドウ`では「名前のないシャドウ（クラシック）」を選択します。デバイスシャドウにより、AWS IoT Coreと接続されたデバイスはその状態をAWS IoT Core側で管理する状態と同期することができます。以下のAWSの資料もわかりやすいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/18303f18-767c-bf6f-c1f1-b8fe372736fd.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/19feb2d5-18e6-d12c-8ce8-7564fa33ae81.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/6f337398-d810-c214-33cd-8d68a5866442.png)

次にデバイス証明書を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/d7543fe6-66bd-ad19-26c4-582c83fdabbc.png)
（後ほど、ダミーデバイス側にも証明書を設定します。この設定によりデバイス側とAWS IoT Core側との間で認証が実施されます）

次にこちらの証明書を先ほど作成したPolicyにアタッチします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/80d1ed90-730d-a1e2-dbfc-b1b913cc1295.png)
その後上画像の「Create thing」をクリックすると画面がポップアップされるので、その画面から以下をダウンロードします。（但し、本ハンズオンではパブリックキーは利用しない）
* デバイス証明書
* パブリックキーファイル
* プライベートキーファイル

### デバイスの設定
以降ではデバイスを起動し、AWS IoT Core側と接続していきます。
まずは先ほどダウンロードした証明書のプライベートキーファイルをCloud9側にアップロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/d7813e2a-0201-d4ed-a5e1-1425e0908693.png)

次にルートCA証明書をCloud9にダウンロードします。
```
cd ~/environment/dummy_client
wget https://www.amazontrust.com/repository/AmazonRootCA1.pem -O certs/AmazonRootCA1.pem
```
### デバイスの起動
まずは以下のコマンドでデバイスを起動していきます。本コマンドを実行した時点で、このダミーデバイスはAWS IoT Core側と接続され、Pythonで記載されているようにメッセージを送信します。
```
cd ~/environment/dummy_client/
python3 device_main.py --device_name {ご自分の作ったモノ名} --endpoint {AWS IoTのendpoint_url}
```
device_main.pyでは以下のようにトピックが送信されています。`BASE_TOPIC`には`data/`が入っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/eb8137e5-7075-2fe2-221c-79b9872b4d91.png)

### 疎通の確認
ダミーデバイスから送信されているトピックを受信できているか、AWS IoT Core側で確認します。
`AWS IoT`>>`Test`>>`MQTT test client`の画面で先ほどのTOPIC名を入力して受信（Subscribe）します。以下の画像のように、仮想的なデバイスから、クラウド(AWS IoT Core)へメッセージが送信されていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/b8b613e7-a088-220f-9319-84ed96e91ea5.png)

### デバイスシャドウの確認
次にAWS IoT Core側からデバイスを制御するために、先ほど作成したデバイスシャドウを用いて挙動を確認してみます。`AWS Iot`>>`Manage`>>`Things`からモノを選択し、`Device Shadows`のタブをクリックし、作成したデバイスシャドウを選択すると`Device Shadow document`の画面が表示され、こちらのドキュメントを変更することで、デバイス側を制御できます。以下のように変更してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/07fdd028-e4c1-8be2-491a-f4926d5bd2a9.png)
`reported`の値が`5`だったので、これまでは5秒間隔でメッセージが送信されていました。
`desired`の値を`2`に変更したので、2秒間隔でメッセージを送信するよにデバイス側を操作しています。
先ほどと同じようにAWS IoT Coreの画面で受信メッセージを確認すると、timestampの間隔が2秒になっていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/85c7800b-7f58-463f-6f36-366c084f0fef.png)
ここまででIoTデバイス（ダミーデバイス）とAWS IoT Coreとの基本的な接続やメッセージの送受信までの構築が完了しました！

## IoTアプリケーションの作成
ここからはさらに深堀して、IoTデバイスとつながるアプリケーションの構築をしていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/f835300f-ca89-d47a-208e-6902c384f5b2.png)

### Kinesis Data Streamsの作成
まずはKinesis Data Streamsの作成です。コンソールから簡単に作成できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/d5b8efcf-f823-e2bd-f13b-32b10fb53169.png)

### DynamoDBの作成
DynamoDBもコンソールから作成します。`テーブル名`や`パーティションキー`、`ソートキー`などは画像のように設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/d51b9055-8e50-246e-8ba6-528e5a4fc114.png)

### IAMロールの設定
次にIAMロールを作成していきます。詳細な手順は[公式ハンズオン](!https://catalog.us-east-1.prod.workshops.aws/workshops/b3e0b830-79b8-4c1d-8a4c-e10406600035/ja-JP/phase4/step3)を参照してください。今回は以下2つのロールを作成しました。
* Lambdaで利用するロール
    * `AWSLambdaBasicExecutionRole`　ポリシー
    * `AmazonKinesisReadOnlyAccess`　ポリシー
    * `AmazonDynamoDBFullAccess`　ポリシー
* AWS IoT用のロール
    * `AWSIoTThingsRegistration`　ポリシー
    * `AWSIoTLogging`　ポリシー
    * `AWSIoTRuleActions`　ポリシー

### Lambdaの設定
`python3.8`でLambdaを作成していきます。Lambdaのトリガーには上記で作成したKinesis Data Streamsを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/49e5c60c-0d81-ca00-6d60-297df6548159.png)
Lambdaの中身はハンズオンで用意されている[こちらのコード](!https://awsj-iot-handson.s3-ap-northeast-1.amazonaws.com/aws-iot-core-workshop/lambda_function.py)を利用します。コードの中身で環境変数からDynamoDBのテーブル名を取得している箇所があるので、以下のように環境変数を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/1ba1ffca-6110-caaf-c844-77438ed5d7ed.png)

### AWS IoT Core の設定
次にAWS IoT Coreの設定を実施し、AWS IoT Coreで受信するデータをKinesis Data Streamsに受け渡しできるよにします。`AWS IoT`>>`Message routing`>>`Rules`からルールを以下のように作成します。こちらのルールもSQLライクのステートメントで記述できるので、とても使いやすいと感じました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/74a818b1-c9da-e1f2-8c52-f22974036374.png)
ちなみにこちらの`Rules`では以下のように多様なAWSサービスとネイティブに統合できます。この辺りの各サービス間での接続のしやすさもAWSの使いやすいポイントだと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/c84913b9-aed3-93bf-8735-33347cd17461.png)

ここまででダミーデバイスから送信されるメッセージをDynamoDBに格納するまでの部分が構築できました。各サービスのMonitoringから想定通りに動いていることも確認できました（確認部分は記事では割愛）。

### API用のLambdaの設定
次にWebブラウザからコールされるAPI用のLambdaを設定していきます。このLambdaではDynamoDBに格納されている、ダミーデバイスからの情報を返却します。こちらのLambdaのコードも[公式ハンズオン](!https://catalog.us-east-1.prod.workshops.aws/workshops/b3e0b830-79b8-4c1d-8a4c-e10406600035/ja-JP/phase4/step6#4.6.3)にて用意されているコードを利用していきます。先ほどのLambdaと同じように、環境変数にDynamoDBのテーブル名をセットして完了です。

### API Gatewayの設定
APIGatewayの詳細な設定方法は[公式ハンズオン](!https://catalog.us-east-1.prod.workshops.aws/workshops/b3e0b830-79b8-4c1d-8a4c-e10406600035/ja-JP/phase4/step7)から参照してください。RESTで上記で作成したLambdaを呼び出すAPIをデプロイすればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/787355dd-195f-edea-6023-f3183a4e8ce9.png)

公式ハンズオンで用意されているhtmlファイルにAPI先を指定してページを開くと、以下のようにDynamoDBの中身を参照できることが確認できました！APIが挙動しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/5af2d3ea-e67b-e176-f3a9-10ae0ff145b7.png)

## ニアリアルタイムなデータの可視化
`Amazon OpenSearch Service`を利用したこちらのハンズオン部分は、用意されているCloudFormationテンプレートを実行してスタックを作成していく流れになります。本記事では内容は割愛し、詳細は[公式ハンズオン](!https://catalog.us-east-1.prod.workshops.aws/workshops/b3e0b830-79b8-4c1d-8a4c-e10406600035/ja-JP/phase5)を参照ください。

## データレイクの作成
ダミーデバイスから送信されるデータを溜めていくデータレイクを作成していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/411bca26-a43c-18b1-e288-d4d7b145c179.png)
尚、AWS IoT Coreから直接Amazon S3にデータを格納するような構成も可能ですが、データを数分おきにまとめて格納したり、圧縮してデータを格納したりするなどの設定がAmazon Kinesis Firehoseを挟むことで簡単じに実装できます。

### S3バケットの作成
シンプルにS3バケットを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/48ab0fca-b241-f168-eddb-2673b0763da2.png)

### Kinesis Data Firehoseの設定
`Amazon Kinesis`>>`Delivery streams`から作成していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/39c01ffc-18c2-7616-1fe7-e2bc3b4d40f1.png)
Bufferの設定や圧縮なども簡単に設定できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/468dcc85-1635-cde6-42e9-8b0d7fd6a827.png)

### IoT ルールの作成
Kinesis Data Firehoseにメッセージを受け渡すルールをAWS IoT Coreに設定していきます。既存のKinesis Streamに加えて、Kinesis FIrehose streamへのアクションをルールに追加しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/1c57a901-e54c-2427-ab21-923268887ab4.png)

### 動作確認
S3バケットを確認すると、gzで圧縮されたデータが溜められているのが確認できました！データレイクも構築完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/b6a4a9e3-6b79-1463-14ac-326018239145.png)

## おわりに
本ハンズオンを通して、AWS IoT Coreの基本的な使い方だけでなく、AWS IoT Coreと接続したWebアプリケーションの構築方法やデータレイクの構築方法なども具体的なイメージとともに学習できました。IoTアプリケーションと聞くと、業務アプリケーションと比べて難易度が高いイメージがありましたが、AWSの各サービスを連携して構築できることが実体験を通して理解できたのが何より大きかったです。