CO2センサーの値をESP32-WROVER-E経由でAWS IoTに送ってみる

# はじめに
CO2センサー（MH-Z19C）を購入したので、AWS IoTのお勉強も兼ねて、値を送ってみました。
なお。今回は送った値をS3に保存するようにしました。

# 準備
## ハードウェア
今回使用したハードは以下の通りです。

- ESP32-WROVER-E開発ボード（8MB）
    - <font color="FF0000">【注意】ハードウェアシリアルポートが1つしかないため、今回はPWMで接続しています</font><br/>ソフトウェアシリアル版は[こちら](https://qiita.com/mine820/items/205fdc83dbb2731b84db)
- MH-Z19C
- ブレッドボード
- ジャンパーケーブル
- USBケーブル
- PC（Windows）

## ソフトウェア
ArduinoIDEを使用するに当たり、いくつかライブラリをインストールしました。

- MHZ19（[github](https://github.com/crisap94/MHZ19)からzipをダウンロードし、インストール）
- ArdionoJson
- PubSubClient
- WiFiClientSecure
- EspSoftwareSerial

# 配線
ESP32とMH-Z19Cの接続は以下の通りです。

- ESP32のIO14番をMH-Z19CのPWMに接続
- ESP32の5VをMH-Z19CのVinに接続
- ESP32のGNDをMH-Z19CのGNDに接続

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/e30091dd-fb1f-8007-b214-d1c2c5f2a9a0.png)

# プログラム
まず、MH-Z19ライブラリを修正します。
オリジナルでは、計測範囲が2,000PPM固定になっているため、今回使用するMH-Z19Cに合わせ、5,000PPMまで測れるようにします。

```MHZ19.h
（途中省略）

	// Pwm Data Flag
	uint8_t PWM_DATA_SELECT = MHZ19_PWM_DATA::CALC_5000_PPM; // 2000→5000
};

#endif
```

Ardiono側のプログラムは以下の通りです。

```c:awsiot.ino
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <Arduino_JSON.h>

#include <MHZ19.h>

// WiFi設定情報（自分の無線LAN環境）
const char* SSID = "mySSID";  // 変更すること
const char* PASSWORD = "myPASSWORD";  // 変更すること

// AWS IoT設定情報
const char* AWS_ENDPOINT = "xxx.iot.ap-northeast-1.amazonaws.com";  // 変更すること
const int   AWS_PORT     = 8883;
const char* PUB_TOPIC    = "topic/fromDevice";
const char* SUB_TOPIC    = "topic/fromCloud";
const char* CLIENT_ID    = "esp32";

const char* ROOT_CA = "-----BEGIN CERTIFICATE-----\n" \
"****************************************************************\n" \
"-----END CERTIFICATE-----\n";  // 変更すること

const char* CERTIFICATE = "-----BEGIN CERTIFICATE-----\n" \
"****************************************************************\n" \
"-----END CERTIFICATE-----\n";  // 変更すること

const char* PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n" \
"****************************************************************\n" \
"-----END RSA PRIVATE KEY-----\n";  // 変更すること

// MQTT設定
#define QOS 0 // 届こうが届くまいが1回だけメッセージをPublishする（0か1を指定可）
WiFiClientSecure httpsClient;
PubSubClient mqttClient(httpsClient);

// MH-Z19C
const int pwm_pin = 14;
MHZ19* myMHZ19 = new MHZ19(pwm_pin);
char pubMessage[128];

void setup_wifi(){
  Serial.print("Connecting to ");
  Serial.println(SSID);

  // ESP32でWiFiに繋がらなくなるときのための対策
  WiFi.disconnect(true);
  delay(1000);

  WiFi.begin(SSID, PASSWORD);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void setup_awsiot(){
  httpsClient.setCACert(ROOT_CA);
  httpsClient.setCertificate(CERTIFICATE);
  httpsClient.setPrivateKey(PRIVATE_KEY);
  mqttClient.setServer(AWS_ENDPOINT, AWS_PORT);
  mqttClient.setCallback(mqttCallback);
}

void connect_awsiot() {
  while (!mqttClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    if (mqttClient.connect(CLIENT_ID)) {
      Serial.println("Connected.");
      mqttClient.subscribe(SUB_TOPIC, QOS);
      Serial.println("Subscribed.");
    } else {
      Serial.print("Failed, rc=");
      Serial.print(mqttClient.state());
      Serial.println(" Try again in 5 seconds");
      // 5秒後にリトライ
      delay(5000);
    }
  }
}

void mqttCallback (char* topic, byte* payload, unsigned int length) {
    Serial.print("Received. topic=");
    Serial.println(topic);

    char subMessage[length];
    for (int i=0; i < length; i++) {
      subMessage[i] = (char)payload[i];
    }
    Serial.println(subMessage);

    // JSON前提でパースする
    JSONVar obj = JSON.parse(subMessage);
    // 今は受け取っても何も処理しない
}

void setup(){
  Serial.begin(115200);
  delay(100);
  
  setup_wifi();
  setup_awsiot();
}

void loop(){
  // CO2取得
  int CO2 = myMHZ19->getPpmPwm();

  // Publishするメッセージの作成
  sprintf(pubMessage, "{\"co2\": %d}", CO2);
  Serial.print("Publishing message to topic ");
  Serial.println(PUB_TOPIC);
  Serial.println(pubMessage);

  mqttClient.loop();
  while (!mqttClient.publish(PUB_TOPIC, pubMessage)) {
    if (!mqttClient.connected()) {
      connect_awsiot();
    }
    mqttClient.loop();
  }
  Serial.println("Published.");

  delay(1000*60*5); // 5min
}
```

- SSIDに、接続するWi-FiのSSIDを設定すること
- PASSWORDに、接続するWi-Fiのパスワードを設定すること
- AWS_ENDPOINTに、デバイスデータエンドポイント（後述）を設定すること
- ROOT_CAに、保存したルートCA（後述）を設定すること
- CERTIFICATEに、ダウンロードしたxxx.pem.crt（後述の「このモノの証明書」）を設定すること
- PRIVATE_KEYに、ダウンロードしたxxx-private.pem.key（後述の「プライベートキー」）を設定すること

なお、AWS IoTからのメッセージを受信できるようになっていますが、今は何もしていません。
（将来用です）

# AWS IoT Coreの設定
## ポリシーの作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/3fa1ebcf-c438-93e9-1b4e-a89252b5825a.png)
始めにポリシーを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/4b299dfa-6981-797f-9a39-3507ace2117e.png)
適当な名前を指定し、以下の項目をそれぞれ指定します。

- アクションに「iot:*」を指定する
- リソースARNに「*」を指定する
- 効果を「許可」する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/6db78ea6-fc18-0fd4-2315-26ac4106a0fd.png)
結果、ポリシーが作成されました。

## モノの作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/09f272f9-ea77-4330-415a-459258a1750e.png)
次にモノを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/80424da0-562e-508a-8a11-244a0fa0ffa3.png)
今回は「単一のモノを作成する」を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/31cea8ce-60f8-3a8f-9334-4105ada6ab05.png)
適当な名前を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/f719fc2e-a227-7057-c205-2345512ff04c.png)
続いて、1-Click証明書を作成するために「証明書を作成」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/0d9d6b82-b4e8-4f68-2ac9-a3f9c69ccd7c.png)
作成された証明書をすべてダウンロードします。
また、AWS IoTのルートCAもダウンロードします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/c7ec3f0d-4acd-308c-2b61-d9c521984062.png)
ルートCAは、この画面の「Amazon Root CA 1」をダウンロードします。

なお、ここでダウンロードしたファイルの中身を、ArduinoIDEで設定します。

また、「有効化」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/20c203cb-9afb-a80d-923c-1811887f3fb3.png)
「無効化」に表示が変わりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/79cb8dd4-b986-f949-32b4-fdc1d94826dc.png)
最後に、モノにポリシーを設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/985328df-4b37-f56e-5ea5-40184e82375a.png)
結果、モノが作成されました。

## ルールの作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/e572adec-a50c-60ed-1fff-5a71789d3e4e.png)
最後にルールを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/71127b39-609f-3d14-b3b0-5144918aa3c0.png)
まず適当な名前を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/32f4723e-03e5-e5ec-055d-698e8725dfe0.png)
続けて、ルールクエリステートメントを設定します。
今回は、ESP32から送られてきたメッセージ全部を使用するため、クエリを「SELECT * FROM 'topic/fromDevice'」としました。
（ESP32からMQTTで送るとき、「topic/fromDevice」にpublishしているため）

次に、アクションを設定するため「アクションの追加」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/0eec072d-bb6c-6534-96a9-8944e1d98d7c.png)
送り先として「S3」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/a1f9518d-b028-6421-cfee-17a5740b8ca8.png)
送り先のS3のバケットとキー（ファイル名みたいなもの）を指定します。
今回指定したキーには、モノの名前と時刻を使用して一意になるようにしました。

続けてロールを設定するため、「ロールの作成」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/1d7c2f64-c39d-0aa1-96b9-9fa6d1e9d0d8.png)
適当な名前を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/47d2ca56-c14b-7f96-9a22-42a4fb3fda67.png)
作成したロールがアタッチされます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/c9e3d750-2e11-a57e-0e95-89873ea3c7a5.png)
結果、アクションが設定されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/52035ee8-af40-f5c3-d35e-4ecf3702a9eb.png)
結果、ルールが作成されました。

## デバイスデータエンドポイントの確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/366faba5-102b-b552-0ef8-98ec6a4e5177.png)
ArduinoIDEで設定するデバイスデータエンドポイントの値を確認します。

# 動作確認
ちゃんと動いているかの確認のため、ESP32からデータを送ってみます。

まず、AWS IoT Coreに届いたかどうかは、テストで確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/299cfb25-adfc-55de-ef9d-863f1bc4765b.png)
トピックのフィルターに「topic/fromDevice」を指定し、「サスクライブ」を選択すると、届いたメッセージが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/8e194154-ed7d-503b-06c4-e7a3e3dbcc7f.png)
また、S3でファイルが保存されているかを確認します。

```1620486637323.txt
{"co2": 1470}
```

テストで受け取った値と同じものが、S3のファイルにも設定されていました。

# まとめ
実際に使用する際にはグラフ等で可視化することが多いので、IoT Coreから直接ElasticSearchに送ったり、いったんDynamoDBに入れたりすることになると思います。

# 参考URL
https://qiita.com/nara256/items/a3cecc5fbf043b2d042c
https://scrapbox.io/kn1cht/MH-Z19_CO2%E3%82%BB%E3%83%B3%E3%82%B5_on_ESP32