ESP32で温度と湿度をAWS IoTへ送ってグラフ化してみた

# はじめに

こちらの続きです。
[ESP-IDFを使ってESP32からAWS-IoTにつないでみた](https://qiita.com/hisashij/items/f0b5d0d53ed70aa59b12)

上記で利用したesp-aws-iotのサンプルをカスタマイズして、温湿度センサのデータをAWS IoTへ送ってみました。

【今回の環境】

- 開発環境：Windows 10 + MSP-IDF
- ボード：[ESP32-DevKitC-32D](http://akizukidenshi.com/catalog/goods/search.aspx?search=x&keyword=M-13628)
- センサ：[Adafruit Si7021](https://www.adafruit.com/product/3251)

センサはアスクルで買いました。アスクルは文房具だけじゃないのですね。

# センサの接続

![IMG_20200809_155521.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679422/f25720a3-b28e-f203-48d0-1625c729ab6d.jpeg)

|ESP32|Si7021|
|:--|:--|
|3V3|VIN|
|GND|GND|
|IO21|SDA|
|IO22|SCL|

# センサのデータを読み取るプログラム

Adafruit Si7021を操作するライブラリは、ArduinoやPython用のものはありましたが、ESP-IDFで使えそうなものはありません。とはいえI2Cの操作を一から書くのも自身が無かったので、ググって以下の記事を参考にさせていただきました。
[Hardware Interactions: Part 4 – I2C Temperature and Humidity Sensor](https://a2i2.deakin.edu.au/2019/12/03/raspberry-pi-hardware-interactions-part-4-i2c-temperature-and-humidity-sensor/)

まず、サンプルの`subscribe_publish_sample.c`の中に、以下３つの関数を追加定義。

- `int initSensor(void)`
- `float readTemp(void)`
- `float readHumid(void)`

```cpp
#include "driver/i2c.h"

// Commands for Si7021
#define SI_7021_ADDRESS                 0x40
#define SI_7021_MEASURE_HUMIDITY        0xE5
#define SI_7021_MEASURE_TEMPERATURE     0xE3
#define SI_7021_TIMEOUT                 (1000 / portTICK_RATE_MS)

static int initSensor(void)
{
	esp_err_t			error;

    i2c_config_t config;
    config.sda_io_num = 21;
    config.sda_pullup_en = GPIO_PULLUP_ENABLE;
    config.scl_io_num = 22;
    config.scl_pullup_en = GPIO_PULLUP_ENABLE;
    config.mode = I2C_MODE_MASTER;
    config.master.clk_speed = 100000;

    error = i2c_param_config(I2C_NUM_0, &config);
    if (error != ESP_OK) {
        ESP_LOGI(TAG, "Failed to configure shared i2c bus: %s", esp_err_to_name(error));
        return -1;
    }
    
    error = i2c_set_timeout(I2C_NUM_0, 100000);
    if (error != ESP_OK) {
        ESP_LOGI(TAG, "Failed set timeout for i2c bus: %s", esp_err_to_name(error));
        return -1;
    }
    
    error = i2c_driver_install(I2C_NUM_0, config.mode, 512, 512, 0);
    if (error != ESP_OK) {
        ESP_LOGI(TAG, "Failed to install driver for i2c bus: %s", esp_err_to_name(error));
        return -1;
    }
    
    return 0;
}

static float readTemp(void)
{
	i2c_cmd_handle_t	handle;
	esp_err_t			error;
	
	// Write measure humidity command
	handle = i2c_cmd_link_create();
	i2c_master_start(handle);

	i2c_master_write_byte(handle,
			SI_7021_ADDRESS << 1 | I2C_MASTER_WRITE,
			I2C_MASTER_ACK);
	i2c_master_write_byte(handle,
			SI_7021_MEASURE_TEMPERATURE,
			I2C_MASTER_ACK);

	i2c_master_stop(handle);
	error = i2c_master_cmd_begin(I2C_NUM_0, handle, SI_7021_TIMEOUT);
	i2c_cmd_link_delete(handle);
	if (error != ESP_OK) {
		ESP_LOGI(TAG, "Failed to write temperature command: %s", esp_err_to_name(error));
		return 0;
	}
	vTaskDelay(50 / portTICK_RATE_MS);

	// Read two bytes from the temperature and humidity sensor
	uint8_t tmpMSB;
	uint8_t tmpLSB;

	handle = i2c_cmd_link_create();
	i2c_master_start(handle);

	i2c_master_write_byte(handle,
		SI_7021_ADDRESS << 1 | I2C_MASTER_READ,
		I2C_MASTER_ACK);

	i2c_master_read_byte(handle, &tmpMSB, I2C_MASTER_ACK);
	i2c_master_read_byte(handle, &tmpLSB, I2C_MASTER_NACK);

	i2c_master_stop(handle);
	error = i2c_master_cmd_begin(I2C_NUM_0, handle, SI_7021_TIMEOUT);
	i2c_cmd_link_delete(handle);
	if (error != ESP_OK) {
		ESP_LOGI(TAG, "Failed to read temperature: %s", esp_err_to_name(error));
		return 0;
	}

	float temp = ((uint16_t) tmpMSB << 8) | (uint16_t) tmpLSB;
	temp *= 175.72;
	temp /= 65536;
	temp -= 46.85; 
	
	return temp;
}

static float readHumid(void)
{
	i2c_cmd_handle_t	handle;
	esp_err_t			error;
	
	// Write measure humidity command
	handle = i2c_cmd_link_create();
	i2c_master_start(handle);

	i2c_master_write_byte(handle,
			SI_7021_ADDRESS << 1 | I2C_MASTER_WRITE,
			I2C_MASTER_ACK);
	i2c_master_write_byte(handle,
			SI_7021_MEASURE_HUMIDITY,
			I2C_MASTER_ACK);

	i2c_master_stop(handle);
	error = i2c_master_cmd_begin(I2C_NUM_0, handle, SI_7021_TIMEOUT);
	i2c_cmd_link_delete(handle);
	if (error != ESP_OK) {
		ESP_LOGI(TAG, "Failed to write humidity command: %s", esp_err_to_name(error));
		return 0;
	}
	vTaskDelay(50 / portTICK_RATE_MS);

	// Read two bytes from the temperature and humidity sensor
	uint8_t humMSB;
	uint8_t humLSB;

	handle = i2c_cmd_link_create();
	i2c_master_start(handle);

	i2c_master_write_byte(handle,
		SI_7021_ADDRESS << 1 | I2C_MASTER_READ,
		I2C_MASTER_ACK);

	i2c_master_read_byte(handle, &humMSB, I2C_MASTER_ACK);
	i2c_master_read_byte(handle, &humLSB, I2C_MASTER_NACK);

	i2c_master_stop(handle);
	error = i2c_master_cmd_begin(I2C_NUM_0, handle, SI_7021_TIMEOUT);
	i2c_cmd_link_delete(handle);
	if (error != ESP_OK) {
		ESP_LOGI(TAG, "Failed to read humidity: %s", esp_err_to_name(error));
		return 0;
	}

	float humid = ((uint16_t) humMSB << 8) | (uint16_t) humLSB;
	humid *= 125;
	humid /= 65536;
	humid -= 6; 
	
	return humid;
}
```

そして、whileループの手前で`initSensor()`を呼んでセンサを初期化し、whileループの中でメッセージを送っている部分を以下で置き換えます。

```cpp
    sprintf(cPayload, "{\"Temperature\": %.02f, \"Humidity\": %.02f}", readTemp(), readHumid());
    paramsQOS1.payloadLen = strlen(cPayload);
    rc = aws_iot_mqtt_publish(&client, TOPIC, TOPIC_LEN, &paramsQOS1);
```

あとはいくつか微修正。
MQTTのトピック名を変更。

```cpp
    const char *TOPIC = "esp32/temphumid";
```

whileループの中の待ち時間を1秒から20秒に変更。

```cpp
    vTaskDelay(20 * 1000 / portTICK_RATE_MS);
```

# ビルドと書込み

ビルドして書込みます。

```
idf.py build
idf.py -p COM3 flash
```

AWS IoTのコンソールで確認すると、良い感じでメッセージが送られてきています。

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679422/17254e08-8055-5b75-d25c-fb941b474ba1.png)

# AWS側でグラフ化

グラフ化に際してはこちらを参考にさせていただきました。
[AWS IoT に上げたデータをCloudWatchで簡単にグラフ化する](https://qiita.com/tatsuhiroiida/items/6f4599174b79bc0750b2)

## AWS IoTで「ルール」を作成

AWS IoTの「ACT」-「ルール」を開き、

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679422/a897fbac-55b0-6879-4062-cc24e461c81d.png)

「ルールの作成」へ。以下の設定をして「ルールの作成」をクリック。

|項目|設定値|
|:--|:--|
|名前|ESP32_TempHumid|
|ルールクエリステートメント|SELECT * FROM 'esp32/temphumid'|
|アクション|CloudWatch Logsにメッセージデータを送信する|
|ロググループ名|ESP32_TempHumid（新規作成）|
|ロール|CloudWatch（新規作成）|

## CloudWatchでグラフ化

続いて、CloudWatchの画面で「インサイト」を開きます。
先ほど作成したロググループを選択し、クエリ欄に以下を入力して「クエリの実行」をクリックすると、ESP32から送られた温度の一分毎の平均値が表示されます。

```
stats avg(Temperature) by bin(1m)
```

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679422/531411b7-078e-1504-8ec0-4e04c8815b40.png)

「ダッシュボードに追加」をクリックし、新しく作ったダッシュボードに折れ線グラフを追加します。

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679422/1a0cdea9-c5ff-27f1-db8c-da6e9501a456.png)

もう一度「インサイト」に戻って、湿度についても同様に追加すると、温度と湿度が良い感じでグラフ表示されるようになりました。

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679422/1fb77a3d-da73-1465-7de4-29b11544b19e.png)

おしまい。