GCPのほうのIoTCoreを使ってみる

#はじめに
こんにちは
RHEMS技研荒木です。
無人島に漂流しすぎて寝不足です。島の名前はI/O島にしました。

備忘録がてらIoTCoreを用いたセンサデータの収集についてまとめました。
2020/03/21現在の情報です。1~2ヶ月のうちに情報が古くなると思いますのでご注意ください。




#構成図
![ReikouzoPNG (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/2de7fc2b-4c9e-ce41-c2d5-19c71e102e05.png)


センサデータを収集するためのスタンダードな構成だと思います。
CloudFunctionはデータ到達確認のため置いています。 ここでは解説しません。


#IoTCore
これはAWSでいうIoTCoreと似たサービスになります。そりゃそうなんですけど。
大きく違う点はレジストリ単位でトピックが決まっている等点になります。AWSではエンドポイントは固定でトピックはデバイスごとに選択できるのですが、GCPではレジストリに属しているデバイスはすべて同じトピックへの送信になります。

設定をしていきます。
まずプロジェクトを作成します。
今回は`qiita-akijin-test`という名前にしました。
![スクリーンショット 2020-03-21 14.16.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/2f51a8a7-a181-edfe-d525-6378fd96a3d9.png)


組織や場所は環境によります。
問題なければ作成してしまいます。

次はIoTCoreを有効にします。
左のナビゲーションメニューよりIoTCoreを探します。
初回では画像のような画面になるので`有効にする`を選択してAPIを有効にします。
![スクリーンショット 2020-03-21 14.17.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/7b6e23bb-ab12-e463-7150-de87bc024064.png)

有効になると下画像の用になりますので上の`レジストリを作成`を選択。
![スクリーンショット 2020-03-21 14.18.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/94e1fa99-82e4-4a96-b49e-0e6763b693f2.png)

すると、レジストリの設定が出るので設定をしていきます。
今回は画像の通りの設定をしました。
MQTTでしか通信する予定がないのでHTTPのチェックは外しておきましょう。
![スクリーンショット 2020-03-21 14.19.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/bf82b787-4c0b-51ea-8880-aaf479589758.png)
![スクリーンショット 2020-03-21 14.19.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/e0bb0366-33d5-4b71-5fda-019cc28d62bc.png)

次にデバイスの登録をするのですが先に証明書関係をクリアしておきましょう。

```
$ openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem
```
```
$ openssl ec -in ec_private.pem -pubout -out ec_public.pem
```
```
$ openssl ec -in ec_private.pem -noout -text
```
上2つのコマンドでサーバー側とデバイス側の証明書を作成、3つ目でデバイスに書き込む用の証明書を書き出しています。
書き出すと画像のように出るので`priv`の方を後ほど使うのでコピーしておきます。
また、先頭が`00`だった場合は`00`を抜かしてコピーします。
![スクリーンショット 2020-03-21 14.28.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/8ebaf996-5d07-3a78-690b-892ebadff6e9.png)

ではデバイスの登録をしていきます。
左のメニューより`デバイス`を選択して`デバイスを作成`を選択します。
![スクリーンショット 2020-03-21 14.55.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/688b646d-5801-f5dd-98bc-9c7608ce1449.png)

デバイスの設定は画像の通りです。
証明書は`公開鍵の形式`を`ES256`にして`ec_public.pem`の中身をペーストします。
各種設定が終わったら作成してしまいましょう。
![スクリーンショット 2020-03-21 14.56.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/dc92de98-b276-c992-1e33-4157609c6b82.png)
![スクリーンショット 2020-03-21 14.56.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/a912d999-bfe3-01c4-ed4f-39bcc72d8b1b.png)

![スクリーンショット 2020-03-21 15.06.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/c34b91fa-7bf8-ba44-7703-fde3990310f8.png)

これでIoTCoreの設定は終了になります。

#Pub/Sub
これはIoTCoreに届いたリクエストを他のGCPのサービスに割り振るために使用します。基本的にIoTCoreを使用する際はPub/Subは必ず使用します。
ここでの設定は特にないです。
IoTCoreのレジストリからPub/Subのページに飛びます。先程作成したトピックで作られているかを確認します。
![スクリーンショット 2020-03-21 15.24.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/13d3dde9-26c8-6fb5-b18e-208c32afdfd6.png)


#BigQuery
先にBigQueryのテーブルを作成します。
左メニューより`BigQuery`を選択。プロフェクト名がリソースに追加されているので選択して右側にある`データセットを作成`を選択します。
![スクリーンショット 2020-03-21 15.42.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/e069ed70-fcb0-a7df-7054-480a3fb78b7c.png)
![スクリーンショット 2020-03-21 15.42.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/25deea03-7aa1-3a78-7090-eb723ee912af.png)

作成したらテーブルを作成します。今回は画像のように設定しました。
![スクリーンショット 2020-03-21 15.43.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/170b068d-8a93-a22c-b448-f0f3e00f86f3.png)

作成するとリソースに画像のようなツリーができていることを確認します。
![スクリーンショット 2020-03-21 15.44.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/e90b872b-8e55-da55-8a30-bd22beea8d21.png)
テーブルが作成されていることも確認しましょう。
![スクリーンショット 2020-03-21 15.44.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/7c9564a5-ba79-667e-d324-15251dc2e4ba.png)

これでBigQueryの設定は終わりです。




#Dataflow
これはサービスとサービスとの間に入れるものになります。テンプレートが予め用意されているので簡単に使用することができます。
使用するテンプレートはPub/Subの指定したトピックに届いたデータをBigQueryに追加してくれるものになります。
今回では、`test-akijin`に届いたメッセージをBigQueryへと送ります。

##Storage
AWSで言うところのS3がこれに当たります。
Dataflowはデータを処理するのにバッファリングをします。なのでバッファを作成する必要があり、Storageにバッファを作成するようになっています。
左のメニューよりStorage-ブラウザを選択。
`バケットの作成`よりバケットを作成していきます。設定は画像のとおりです。
![スクリーンショット 2020-03-21 15.36.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/c6d3b99e-f9cc-fab9-777e-90290ef8dcd6.png)
バケット内にフォルダを一つ作成しておきます。このフォルダがDataflowで使用するバッファになります。
これでバッファは用意されました。


Dataflowの設定に戻ります。
Pub/Subのメニューより`BIBQUERYにエクスポート`を選択。
![スクリーンショット 2020-03-21 15.53.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/7703f23b-2732-7a74-1e90-3b7eff377cbf.png)

するとDataflowの設定に飛ぶので画像のように設定します。
`リージョンエンドポイント`はDataflowの元であるComputeEngineが置かれるリージョンを指定します。なるべく近いリージョンを選ばないと他のサービスとの地理的距離が大きくなってしまうので料金がかさむ要因になります。
`BigQuery output table`では書き込むBigQuery先を選択します。`<プロジェクト名>:<データセット名>.<テーブル名>`の形式で入力するので、`qiita-test-akijin:test.secret_word`となります。
`一時的なロケーション`には先程作成したStorageのバッファの場所を入力します。
![スクリーンショット 2020-03-21 16.45.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/ccd2356e-25d1-d7f5-7b7c-df8cc6974e31.png)



設定し終えたらDataflowの設定は終わりです。
GCP側での設定も終了です。

#デバイスの設定
デバイスはESP32を使い、ArduinoIDEで進めていきます。他のエディタでも同様に進めることができると思います。
IoTCoreでの設定の通り、通信にはHTTPではなくMQTTで通信をしていきます。
通信用のコード・ライブラリも用意されているものを使用します。
https://github.com/GoogleCloudPlatform/google-cloud-iot-arduino
をクローンして通常のライブラリを入れる要領で追加します。

使いしたらサンプル例より、`Esp32-lwmqtt`を開きます。
開くとinoファイルとヘッダーファイルが2つが開かれます。
`ciotc_config.h`がWiFiやGCP，MQTTの設定用ファイルになりますので編集していきます。
17行目以下のところを編集します。

```c
// Wifi network details.
const char *ssid = "SSID";
const char *password = "PASSWORD";

// Cloud iot details.
const char *project_id = "qiita-test-akijin";
const char *location = "asia-east1";
const char *registry_id = "akijin-test";
const char *device_id = "test1";
```
WiFiの設定は飛ばします。
GCPの設定は変数名を参考にして書き換えます。

42行目以下は証明書を貼り付けていきます。

```c
// is probably wrong with your key.
const char *private_key_str =
    "22:d7:a2:63:05:b0:3d:df:53:71:e5:bc:53:cf:0a:"
    "81:00:88:f3:4f:48:4e:82:3b:37:c6:e7:86:e9:02:"
    "fa:e0";
```

`esp32-mqtt.h`の中身も少し書きえます。
setupWifiが名前の通りWiFiの設定なのですが、中身を以下のように書き換えます。
書き換えることによってライブラリのインクルードが必要になるので追加・継承までします。

```c
#include <Client.h>
#include <WiFiManager.h>
#include <WiFiClientSecure.h>

WiFiClientSecure client;
WiFiManager wifiManager;
```

```c
void setupWifi() {
  Serial.println("Starting wifi");

  Serial.println(wifiManager.autoConnect());

  IPAddress ipadr = WiFi.localIP();
  Serial.println(ipadr);
  Serial.println(WiFi.SSID());

  configTime(0, 0, ntp_primary, ntp_secondary);
  Serial.println("Waiting on time sync...");
  while (time(nullptr) < 1510644967) {
    delay(10);
  }
}
```
こうすることによってWiFiの設定を書き込まずともPCやスマホで設定ができるようになりました。詳細は僕の他の記事や`autoconnect`について検索してください。

最後に`Esp32-lwmqtt.ino`を編集してきます。
loopを以下のように変更します。

```c
void loop() {
  mqtt->loop();
  delay(10);  // <- fixes some issues with WiFi stability

  if (!mqttClient->connected()) {
    connect();
  }

  publishTelemetry("{\"Hello\":\"World\"}");
  delay(10000);
}
```
`publishTelemetry();`の引数が送信するデータになります。
BigQueryは追加するデータをJsonで指定するので`"{\"Hello\":\"World\"}"`を引数にします。
10秒に一回送信され、BigQueryのDataflowで設定したテーブルの`Hello`の列に文字列の`World`が追加されます。
Dataflowの方でバッファリングするので10秒に一回追加されるわけではないので注意が必要です。

BigQueryに戻り、プレビューで追加されていることを確認します。
![スクリーンショット 2020-03-21 16.50.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269207/30f948ac-c0d1-54fa-da1b-d3471bb9b8a5.png)
無事に追加されているはずです。
これでセンサデータの収集は終わりです。

#料金
これが一番気になるところです。AWSと比較してみます。
AWSはIoTCoreとDynamoDBを使用したとして比較します。
台数は1台で1時間に12回(5分おき)の送信で1ヶ月での運用と仮定して考えます。
また15分おきにPingを送ります。
メッセージ数12*24*31= 8640が一ヶ月のメッセージ数になります。
一ヶ月は44640分です。
リージョンはいずれも東京です。

##AWS
まずAWSです。
###IoTCore
[https://aws.amazon.com/jp/iot-core/pricing/?nc=sn&loc=4](https://aws.amazon.com/jp/iot-core/pricing/?nc=sn&loc=4)ここを参考にします。

0.096/1000000*44640 = 0.0042...が接続しているための料金

1.20/1000000*86400 = 0.10368 がメッセージ送信のための料金

0.18/1000000*86400*2 = 0.031...がルールの料金（DynamoDBへのインサート）

合計して0.01388USDがIoTCoreの一ヶ月あたりの料金です。

###DynamoDB
[https://aws.amazon.com/jp/dynamodb/pricing/on-demand/](https://aws.amazon.com/jp/dynamodb/pricing/on-demand/)より、書き込みだけを想定します。
`100 万単位あたり 1.4269USD`なので、1.4269/1000000*8640 = 0.0123...がDynamoDBの料金になります。

各サービスの合計で**0.1511USD**が一ヶ月あたりの料金になります。


##GCP
###IoTCore
[https://cloud.google.com/iot/pricing?hl=ja#overview](https://cloud.google.com/iot/pricing?hl=ja#overview)の例から計算します。
4*24*30*1024 =294.9120kB
250MBまで無料なのでIoTCoreでは料金はかからないみたいです。

###Pub/Sub
送受信するデータが10GBに満たないので無料になります。

###Dataflow
DataflowではCompute Engineという他のサービスで料金計算になります。
[ここ](https://www.isoroot.jp/blog/1407/)のサイトを参考にします。
今回はストリーミング処理なので1時間あたり約0.35\$かかります。したがって、0.35*24*31=260.6$となります。
これが一番やばい。
これはテンプレートを使用した際ストリーミング処理になってしまうので1台で運用する際はバッチ処理が適切だと思います。
そうした際はテンプレートを使用しないのでJavaもしくはPythonでコードを書くことになります。

###BigQuery
料金計算が複雑なため公式のサイトを御覧ください。
https://cloud.google.com/bigquery/pricing?hl=ja#on_demand_pricing

合計で**260.6~USD**になります。
Dataflowが主に占めています。台数が少ないならCloudFunctionとかで代用したほうがかなりコストが削減できそう。


#さいごに
プログラムもほぼ書かないでデータの収集までできました。
このぐらいなら初学者の方でも簡単にできそうなのでお試しください。

追記(2020/03/27)
コストカットができたので続編でまとめています。
https://qiita.com/Akijin/items/8ce14a6b1d4ffe468a74