「IoT@Loft ハンズオン  スマートファクトリー  IoT基盤構築プロトタイピング」参加メモ

# はじめに
先日、「[IoT@Loftハンズオン スマートファクトリー IoT基盤構築プロトタイピング](https://iotlofthandson.splashthat.com/)」というイベントに参加してきましたので、忘れないうちにポイントをメモしておきます。

なお当日の様子は、他に以下の方がQiitaに記事を投稿しています。

- [IoT@Loft ハンズオン - スマートファクトリー IoT基盤構築プロトタイピング@目黒セントラルスクウェア 参加記](https://qiita.com/ground0state/items/d7787b439a7a03870a03)
- [AWS IoT@Loft スマートファクトリー IoT基盤構築プロトタイピング を受講したよ（2019年10月2日実施）](https://qiita.com/usashirou/items/426c2e96e7568b708ade)

# アーキテクチャ
今回作成するシステムのアーキテクチャは、以下のようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/ccdad982-1d7f-537b-07d1-955c77f120af.png)

通常エッジ側はRaspberry Piなどを使用するのですが、今回はCloud9で代用します。

# 構築
それぞれのサービスを作成していきます。
作成する順番としては、末端のほうからになります。
（つないでいくため）

## Cloud9
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/347f093e-229f-b9f0-c893-e39e61c7bbac.png)

エッジデバイスの代わりですので、linuxで作成します。
ハンズオンの時間が2時間程度でしたので、自動停止を4時間で設定しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/66250024-6d3f-f03f-51f2-9cd54f5f0b11.png)

## Greengrass
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/2b22563c-0490-cb44-ebdb-3d244eaa32d4.png)

Cloud9上でGreengrassが動作するようにします。

まず、今回使用するGreengrassのグループ、および、Coreを作成します。
指定した名前でモジュールが出来上がりますので、CoreのセキュリティリソースとGreengrass Coreソフトウェアをダウンロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/9d49d1dd-dd20-dcdd-142d-8e23331bd556.png)

Greengrass Coreソフトウェアのダウンロードリンクは、下のほうにスクロールしないと出てこないので注意。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/2ffe85eb-cca8-d5c4-adb4-ab75b9cb0ebb.png)

ダウンロードした2つのtar.zipファイルをCloud9にアップロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/4512885e-bd3c-db07-1870-11eff66c5db7.png)

その後、Cloud9にGreengrass用の環境をセットアップします。詳しくは[こちら](https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/setup-filter.ec2.html)を参照してください。
※今回のハンズオンでは、これらを実行するスクリプトを用意してあり、それを実行するだけでした

Cloud9再起動後、先ほどアップロードしたリソースとGreengrass Coreソフトウェアの展開を行います。
Greengrass Coreはルート「/」に展開、リソースは「/greengrass」に展開します。

さらに、AWS IoTの証明書をダウンロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/046105e1-c1cb-463d-6270-734da8d93c9a.png)

準備がすべて終わったら、Greengrassを再起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/846c3bea-fc3a-9da1-9507-8ceafe3fa191.png)

## Elasticsearch
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/7e2b7b60-4aa7-7c3d-c8fd-702d3e9a2cc2.png)

本番環境ではありませんのでデプロイタイプは「開発およびテスト」にし、外部からアクセスできるように「パブリックアクセス」を選択します。ただし誰でもアクセスできるようにするのは問題ですので、アクセスポリシーを「特定のIPからのドメインへのアクセスを許可」にして、自分のPCのグローバルIPアドレスからだけアクセスできるようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/d7abfd47-0e58-60bd-16a2-57bd430edde8.png)

## Kinesis Firehose
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/918aac9a-d07a-9e35-f6fa-bc618642c861.png)

Elasticsearchが作成されたら、そこに接続するKinesis Firehoseを作成します。
ちなみに、Kinesis Firehoseを使用せずに、直接IoT CoreとElasticsearchをつなぐこともできるのですが、今回は、Elasticsearch側の負荷やデータロスト時の設定が簡単な方法を選んだとのことでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/3984c8e3-9268-ae6e-2923-3202c567f067.png)

出力を先ほど作成したElasticsearchにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/02e07819-6b0f-08d3-4c92-a1cd45f0d0f4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/0e03a6cc-6a4e-d01b-c1c7-a57ef0d48f90.png)

エラーを出力するS3のバケットは、ここで作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/2b13d0c6-a528-2e64-ee2b-4465fa2c4e31.png)

Elasticsearchへの送信間隔などを設定し、最後にロールを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/2431f773-a496-34dc-578a-9d4c988d1a70.png)

## IoT Core
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/c2ccd607-0bf9-f2fd-da32-91fed0946f2a.png)

いよいよ、エッジとクラウドをつなぎます。

送信元は特に指定する必要が無いので、どういったデータを受け取り、どこへ送るのかを設定します。
ここでは送受信のルールとして作成します。
ルールはSQL文で設定します。

```sql:例
SELECT device, value, parse_time("yyyy-MM-dd'T'HH:mm:ss", timestamp) AS timestamp FROM 'data/mine/#'
```

ここではトピック名を「data/mine/」としています。
（後でも出てきます）

また、送信先としてKinesis Firehoseを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/cfdb1c95-9c37-d90a-7867-b83b0fa0d72f.png)

## IoT Analytics
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/881f918d-9c66-7f6f-b7d0-f05397a3f200.png)

受け取ったデータの分析を行います。

今回はクイック作成を利用します。
トピック名は、IoT Coreでの設定に合わせます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/2ef5a18a-f1d9-7ded-4eba-031106b28504.png)

IoT CoreとIoT Analyticsのつなぐ設定を何もしていないように見えますが、実はこのタイミング（チャネルを作成するとき）で自動的に、IoT Core側のルールができています。（便利！）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/61cc625d-b78e-752d-6783-09d7e74514d2.png)

### 分析用データセットの作成
作成したデータセットを選択し、集計用のSQLクエリを作成して分析用のデータセットに出力するようにします。

```sql:例
SELECT
  timestamp,
  devices['sensor1'] as sensor1,
  devices['sensor2'] as sensor2,
  devices['sensor3'] as sensor3,
  devices['sensor4'] as sensor4
FROM (
  SELECT 
    timestamp, 
    map(array_agg(device),array_agg(value)) as devices
  FROM (
    SELECT
      device,
      value,
      timestamp
    FROM mine_20191012_datastore
    WHERE __dt >= current_date - interval '1' day
  ) GROUP BY timestamp
  ORDER BY timestamp ASC
) WHERE devices['sensor1'] IS NOT NULL AND 
devices['sensor2'] IS NOT NULL AND 
devices['sensor3'] IS NOT NULL AND 
devices['sensor4'] IS NOT NULL
```

## Lambda
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/2f565558-85b9-49e3-78b1-1489a885f04e.png)

センサー代わりにLambdaを利用します。
クラウド側でLambdaを作成しGreengrassにデプロイします。

なお、今回はすでに作成済みをLambda関数（Python）を使用しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/1bd00132-3ccb-15e7-e4e8-69a777d60813.png)

※送信するメッセージは「data/mine/sensore1」～「data/mine/sensor4」となっており、それぞれsinカーブ（異常値あり）の値となっています

### Greengrassへの設定

その後、Greengrassに作成したLambda関数を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/103128ed-740a-d63f-e2b9-dc93dfa8f57e.png)

さらに、一部の設定を修正します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/52b7d7a7-d344-926f-3f8f-d889bc1db4a6.png)
また、トピック名を環境変数として設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/0381326e-9f53-fa4f-50d5-91e8e2450382.png)

次に、Greengrassのサブスクリプションを設定します。
今回は、IoT Coreに送るメッセージと送らないメッセージをGreengrassで切り分けます。
判断はトピック名で行います。

指定したトピック名のメッセージをLambdaから受け取り、IoT Coreに送るメッセージを対象とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/1991837b-a4e2-83e6-fbc6-71a675cb4338.png)

最後に、これらの設定をGreengrassにデプロイします。

IoT Coreで受け取ったメッセージは、このようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/ba280826-98c5-247a-9071-757c8b7823cf.png)

## Elasticsearch（可視化）
すでに作成済みのElasticsearchで、受け取ったデータを可視化します。
可視化はKibanaを使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/9c6ad9a4-a17e-9543-70eb-f15475a53d92.png)

ここではKibanaの設定は省略します。
最終的にはこんな感じに可視化できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/10a7ba99-7cbc-7d92-22af-371c417b18c2.png)

## QuickSight
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/21f0667c-8e8c-8f32-985d-7e1308cf769f.png)

今回のハンズオンでは、時間の関係上、省略されました。

## SageMaker
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/a4e43b59-41e5-07cf-c3fe-c5698bde9729.png)

Notebookを使用して、異常値の判定をSageMakerで行います。
NotebookはIoT Analyticsの「ノートブック」から作成します。
（「ノートブック」はIoT Analyticsで、そこにつながっている「ノートブックインスタンス」がSageMaker）

すでにいくつかサンプルのノートブックが用意されているので、今回は異常検出のノートブックを利用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/fc1f70a4-c9e6-966a-ac77-2eb99eb4644d.png)

ノートブックインスタンスが起動したら、ノートブックを表示します。
表示されているコードの一部を、今回のデータ用に変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/e150c695-ea4e-8cc4-e509-5bc010f1865e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/d54512f3-7f0c-263f-aa4b-1b57b9fb6300.png)

すると、ちゃんと今回のデータで異常値の検知ができるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/49adf45d-86f0-a031-c949-d05032084064.png)

## SageMaker（機械学習）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/0f958e4d-8c8f-e98e-2109-160b0aa2e5bb.png)

同じくNotebookを使用して機械学習を行い、モデルをS3に出力します。
出力したモデルは、後ほどエッジで利用します。

機械学習用のノートブックは、先ほどのノートブックインスタンスで実行します。
またノートブックは、すでに用意されているものを使用しました。

ノートブックを実行すると、S3にモデルが出力されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/872239d4-56ba-5eaa-0eb0-47cf71a1795c.png)

## Lambda（機械学習）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/a267b3a1-909e-3a2e-cba1-eda12929620c.png)

先ほどのS3に保存されたモデルを利用して、エッジ側で異常検知を行います。
ここで使用するLambda関数も、すでに用意されているものを使用しました。

### Greengrassへの設定

前回と同じように、作成したLambda関数をGreengerassグループに追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/878697da-2fc1-e3a5-ad39-d2574924424c.png)

また、前回同様に一部の設定を修正します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/83a0949d-9844-b6f2-a51f-d83a772fe9e0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/b07ff8a6-6dea-8750-c827-42171bb79407.png)

さらに、今回は追加したLambda関数に機械学習のリソースを追加します。
先ほど保存したS3のモデルを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/7be2f84a-06c1-7fb6-5135-a61894d910c5.png)

次に、今回もサブスクリプションの設定行います。
今回は、センサーから異常検知のLambda関数へのルートと、異常検知のLambda関数からIoT Coreへのルートの2つを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/3f42025b-ef6b-d627-0291-9feac9bfa23f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/f15047a6-0793-5ac7-e50b-6751658134b1.png)
作成できたらデプロイします。

最後に、IoT Coreのほうで、ちゃんと異常検知した場合のメッセージが届いているか確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/4639f0ef-2e2f-d11b-dada-b3e32c3c404b.png)

# まとめ
やることがいっぱいあって大変ですが、一つ一つ意味を考えながら作成していくと、非常にわかりやすいハンズオンだったなと感じました。