M5Stackでセンサ値をメール通知するアプリ作ってみた

# TL;DR
センサ値（今回は温湿度情報）をとある事情でメール通知したかったため作成しました。
（温度感覚が鈍って自分では熱中症になりそうなことを感じないご高齢の方が身内にいたら使えたりするかも）
手元にあった[M5Stack Gray](https://www.switch-science.com/catalog/3648/)(普通のArduinoの方がネット上にサンプルが溢れているのでより簡単だった模様)と、[GROVE - デジタル温度・湿度センサ](https://www.switch-science.com/catalog/818/)を使って作成。
Gmail送信するため、WiFi環境下でのみ動作しますのでご注意を。

ソースコードはC++に慣れていないのでヨチヨチですが公開してます。（ツッコミなど大歓迎でございます m(_ _)m ）
https://github.com/LittleWat/m5stack-notify-sensors

## セットアップ

### 開発環境構築

Arduino IDEより、VS Codeの方が慣れているのでそちらで環境構築しました。

丁寧な記事があったのでそちらを参考にしました。執筆者に感謝！

1. [【心折れずに】MacでM5stackをはじめる](https://qiita.com/hmmrjn/items/2b2da09eecffcbdbad85)
2. [M5Stackの開発環境を整える - PlatformIO IDE編](https://qiita.com/lutecia16v/items/1c560bdd7eac7ebeaff7)


PlatformIO IDEのLibraryは [`platformio.ini`で指定している](https://github.com/LittleWat/m5stack-notify-sensors/blob/ac81ad8c0a71290c7d192cc518398cb7454f1b4e/platformio.ini#L18)ので追加不要かもですが、下記のライブラリを追加しました。

- M5Stack@^0.2.9
- SimpleDHT@^1.0.12 
- ezTime@^0.8.1 

ちなみに、Serial Monitorの設定や、ログレベルの設定も`platformio.ini`で行なっています。
（ログレベルはソースコードからではコントロールできなかったです。）

```platformio.ini
[env:m5stack-core-esp32]
platform = espressif32
board = m5stack-core-esp32
framework = arduino
monitor_speed = 115200
build_flags = -DCORE_DEBUG_LEVEL=4

lib_deps =
    M5Stack@^0.2.9
    SimpleDHT@^1.0.12
    ezTime@^0.8.1
```

### デバイス

- [M5Stack Gray](https://www.switch-science.com/catalog/3648/)
  - 9軸センサがついてるらしいですが、今回は使用してないです汗
- [GROVE - デジタル温度・湿度センサ(DHT11)](https://www.switch-science.com/catalog/818/)
  - M5Stackを使うのであれば、`DHT11`よりも `DHT12`を使えばサンプルがそのまま動きそうでよかったのですが、たまたま手元にあったのが`DHT11`だったのでそちらを使用。

VCCとGNDは普通につないで、`GROVE - デジタル温度・湿度センサ`の `SIG`を `M5Stack Gray`の5番のポートにつなぎました。

２番ポートにつなぐと、デバイスにソースコード書き込みができない下記エラーが発生します汗

```
`A fatal error occurred: MD5 of file does not match data in flash!
*** [upload] Error 2`
```

調べたところ、どうもESPの問題らしいです。
書き込み時にはケーブルを２番ポートから抜いて、書き込み後に刺せば動作します。が、面倒なので５番ポートを使用しました。

参考

- http://nekomemo2.site/?eid=1643
- https://www.mgo-tec.com/blog-entry-trouble-shooting-esp32-wroom.html/3


### Gmailの設定

あまり好ましくはないのですが、簡単にメールを送るためセキュリティを緩くしました。（メール送信に使うgmailアカウントは最悪乗っ取られてもよいものを作っておくのが良さそうですが）

#### Googleアカウントで、安全性の低いアプリを許可する

![スクリーンショット 2019-10-22 23.28.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104256/10177bcc-d824-312c-1724-cc5ff06d57b9.png)

#### Gmailの設定でIMAPを有効にする

![スクリーンショット 2019-10-22 23.28.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104256/66cd8697-4e62-2c30-e464-5d2acdb60878.png)

以上でセットアップ終了です！

## 実行

下記コマンドを実行し、できた`MyConfig.h`を各自の環境に合わせて編集します。

```bash
git clone https://github.com/LittleWat/m5stack-notify-sensors
cd m5stack-notify-sensors/include
cp MyConfig.h.template MyConfig.h
```

```MyConfig.h
const char* wifi_ssid = "xxx";
const char* wifi_pass = "xxx";
const char* smtp_username = "xxx";
const char* smtp_password = "xxx";
const char* smtp_from_address = "xxx@gmail.com";
const int smtp_port = 465;
const char* smtp_hostname = "smtp.gmail.com";

const char* to_address = "xxx@gmail.com";
const char* subject = "温湿度情報";

const int temperature_upper_limit = 28;
const int temperature_lower_limit = 15;
const int minimum_email_interval_seconds = 60 * 10;  // 10 minutes
const int sensing_interval_milliseconds = 5000;      // should be more than 1500

```

下４行で、下記を設定してます。

- 通知すべき温度の上限値、下限値
- メールを送りまくらないように最低開ける時間（上記では10分に設定）
- センサ値を取得する感覚（上記では5秒に設定）


その後vscodeで開き、下のバーにある `→`ボタン(コンパイルしてm5stackに書き込みするボタン)を押せば動く（ハズです）。

ソースコード作成にあたって下記資料参考にさせていただきました。
- [ELEGOO：Lesson 12](http://shikarunochi.matrix.jp/?p=2586)
- [ESP32からメールを送る](https://kerikeri.top/posts/2017-04-08-esp32-mail/)

[メインのコード](https://github.com/LittleWat/m5stack-notify-sensors/blob/master/src/main.cpp)

```main.cpp
#include <M5Stack.h>
#include <Mailer.h>
#include <SimpleDHT.h>
#include <ezTime.h>

#include "MyConfig.h"

// ref: http://shikarunochi.matrix.jp/?p=2586
// 抵抗は https://www.switch-science.com/catalog/818/　を使っていたら、いらない
// for DHT11,
//      VCC: 5V or 3V
//      GND: GND
//      DATA: 2
// int pinDHT11 = SCL;
int pinDHT11 = G5;
SimpleDHT11 dht11(pinDHT11);

Mailer mail(smtp_username, smtp_password, smtp_from_address, smtp_port,
            smtp_hostname);
Timezone Tokyo;
time_t last_emailed_at;
void send_email(const String content);

void setup() {
  M5.begin();
  M5.Power.begin();

  M5.Lcd.setTextSize(3);

  log_i("Connecting to %s", wifi_ssid);
  WiFi.begin(wifi_ssid, wifi_pass);
  waitForSync();
  log_i("WiFi connected");
  log_i("IP Address: %s", WiFi.localIP().toString().c_str());

  Tokyo.setLocation("Asia/Tokyo");
  Serial.println("Asia/Tokyo time: " + Tokyo.dateTime());
  last_emailed_at = Tokyo.now();
}

void loop() {
  // start working...
  Serial.println("=================================");
  Serial.println("Sample DHT11...");

  M5.Lcd.setCursor(0, 0);

  // read without samples.
  byte temperature = 0;
  byte humidity = 0;
  int err = SimpleDHTErrSuccess;
  if ((err = dht11.read(&temperature, &humidity, NULL)) !=
      SimpleDHTErrSuccess) {
    Serial.print("Read DHT11 failed, err=");
    Serial.println(err);
    M5.Lcd.print("Read DHT11 failed, err=");
    M5.Lcd.println(err);
    delay(1000);
    return;
  }

  // LCD display
  M5.Lcd.print((int)temperature);
  M5.Lcd.print(" *C, ");
  M5.Lcd.print((int)humidity);
  M5.Lcd.println(" H");

  Serial.print("Sample OK: ");
  Serial.print((int)temperature);
  Serial.print(" *C, ");
  Serial.print((int)humidity);
  Serial.println(" H");

  // 経過時間を計算しそれが一定以上、かつ温度が閾値を超えていたらメール送信
  auto elapsed_seconds = difftime(Tokyo.now(), last_emailed_at);

  String email_content = "温度: ";
  email_content += (int)temperature;
  email_content += "℃、湿度: ";
  email_content += (int)humidity;
  email_content += "％です。\n";
  Serial.println(email_content);

  log_d("経過時間：%.1f秒", elapsed_seconds);
  if (elapsed_seconds > minimum_email_interval_seconds) {
    if (temperature > temperature_upper_limit) {
      send_email(email_content + "暑いので冷房を強くしてください。");
    } else if (temperature < temperature_lower_limit) {
      send_email(email_content + "寒いので暖房を強くしてください。");
    }
  }
  // DHT11 sampling rate is 1HZ.
  delay(sensing_interval_milliseconds);
}

void send_email(const String content) {
  mail.send(to_address, subject, content);

  M5.Lcd.println("");
  M5.Lcd.println("Emailed at ");
  M5.Lcd.println(Tokyo.dateTime());

  last_emailed_at = Tokyo.now();
}
```

# 所感
ネット上の記事を漁れば手軽に色々と作れそうな印象を受けました。先人に感謝。今後も色々と作っていきたいですね。

今回、時間が一番かかった気がするのは、地味に下記の文字列結合。
長くてダサいですが一応動いたのでよしとしました。

```c++
String email_content = "温度: ";
email_content += (int)temperature;
email_content += "℃、湿度: ";
email_content += (int)humidity;
email_content += "％です。\n";
```

%dとか使ってやったりして色々と詰まりました汗。。
Arduinoは`stdio:string`ではなく、`String`を使わねばならないみたいですね。（慣れている人からしたら当然のことなのでしょうが...）
もっといい書き方などありましたら教えてくださいませ。。m(_ _)m

PythonやSwiftの手軽さは魅力的ですが、c++（普通の意味で）チョットデキルくらいにはなりたいものです。