M5StickCで温度・湿度・気圧を測定して通知する（テーマは定時運行、LINE Notify）

##はじめに
M5StickCとENV.Ⅱ（SHT30/BMP280）ユニットを使って温度・湿度・気圧を測定し、定期的にLINE Notifyで通知するスケッチを作ってみました。
##実現したいこと
・温度・湿度・気圧を計測し、決まった時刻にLINEに通知する。
・頻繁に充電する必要がないように、通知後、次の計測時間までの間はスリープする。
##参考にさせていただいた情報
・ESP32とLINE Notifyを使ってナースコール的なのを作る （@From_F さん）
　　https://qiita.com/From_F/items/6fc59c50f139f40e52dc
・M5stack（ESP32)で朝顔水やりロボットを作ってみた（[おぎモトキ](http://ogimotokin.hatenablog.com/entry/profile "おぎモトキ")さん）
　　http://ogimotokin.hatenablog.com/entry/2018/08/16/081029
・M5Stack社ENV II Unitを使う（たなかまさゆき[@tnkmasayuki](https://twitter.com/tnkmasayuki)さん）
　　https://lang-ship.com/blog/work/m5stack-env-ii-unit/
・「猫の食生活」を超音波距離センサーでLINEに通知してみた（はとね[@hatone](https://twitter.com/hatone)さん、きょろ[@kyoro353](https://twitter.com/kyoro353)さん）
　　https://next.rikunabi.com/journal/20170719_t12_iq/

##動作中の画面など
・M5StickCのディスプレイ
![IMG_9447_t.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677344/32933fb5-140b-bbb5-3b0f-eb18a5b32497.jpeg)
・ENV.Ⅱ ユニット
![IMG_3581_t.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677344/8f313085-72ce-9a8f-abb3-1e077393771c.jpeg)
・LINEの画面
![IMG_3892_t.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677344/7f81d228-a0b3-eb33-c3dc-eafa3fa34dd5.jpeg)
##スケッチ

```c:ENV2unit_2.ino
/*
  Please install the < Adafruit BMP280 Library > （https://github.com/adafruit/Adafruit_BMP280_Library）
                     < Adafruit SHT31 Library >  （https://github.com/adafruit/Adafruit_SHT31）
  from the library manager before use.
*/

#include <M5StickC.h>
#include <Wire.h>
#include <Adafruit_BMP280.h>
#include <Adafruit_SHT31.h>

#include <ssl_client.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <esp_deep_sleep.h>
#include <time.h>

Adafruit_SHT31 sht3x = Adafruit_SHT31(&Wire);  // 温度と湿度
Adafruit_BMP280 bmp  = Adafruit_BMP280(&Wire); // 気圧
 
float tmp = 0.0;
float hum = 0.0;
float pressure = 0.0;

// wifiの設定
const char* ssid     = "xxxxxxxxxx"; // WiFiのSSID
const char* password = "xxxxxxxxxx"; // WiFiのパスワード

// 時刻の取得設定
const char* ntpServer = "ntp.jst.mfeed.ad.jp"; // NTPサーバー
const long  gmtOffset_sec = 9 * 3600;          // 時差９時間
const int   daylightOffset_sec = 0;            // サマータイム設定なし

void setup() {
  M5.begin();
  Wire.begin(32, 33); // I2C通信 ピンソケット SDA:IO32 SCL:IO33
  
  M5.Lcd.setRotation(1); // 1:HOMEボタンが右
  M5.Axp.ScreenBreath(9);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0, 0, 1);
  
  while (!bmp.begin(0x76)) {
    M5.Lcd.println("Could not find a valid BMP280 sensor, check wiring!");
  }
  while (!sht3x.begin(0x44)) {
    M5.Lcd.println("Could not find a valid SHT3X sensor, check wiring!");
  }
  
  wifi_connect();
  
  // 時刻の取得
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

  struct tm timeInfo;
  if (getLocalTime(&timeInfo)) {
    M5.Lcd.print("NTP : ");
    M5.Lcd.println(ntpServer);
  }
  
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0, 0, 2);
  M5.Lcd.println("ENV2 Unit");
}

void loop() {
  M5.update();
  
  // 気温と湿度を取得
  tmp = sht3x.readTemperature();
  hum = sht3x.readHumidity();
  
  // 気圧の取得
  pressure = bmp.readPressure();
  
  delay(1000); // 1秒待ち
  
  // float から String へ
  String sTmp = String(tmp - 0,1); // 補正（− 0度）かつ小数点以下１桁表示
  String sHum = String(hum + 0,1); // 補正（+ 0％）かつ小数点以下１桁表示
  String sPressure = String((pressure + 0) / 100,0); // 補正（+ 0）かつ小数点以下０桁表示

  // ディスプレイ用文字列
  String dMessage = String("From M5StickC\n") + "T:" + sTmp + ", H:" + sHum + ", P:" + sPressure + ".";
  
  // LINE通知用文字列（「℃」「％」部分は、パーセントエンコーディング）
  String NotifyMessage = String("From M5StickC\n") + sTmp + "%E2%84%83" + ", " + sHum + "%EF%BC%85" + ", " + sPressure + "hPa";
  
  M5.Lcd.setCursor(0, 20, 2);
  M5.Lcd.println(dMessage); // 文字列をディスプレイに表示

  // 時刻を取得
  struct tm timeInfo;
  getLocalTime(&timeInfo);

  if (timeInfo.tm_min == 1){ // 毎時1分のときだけ通知判定する
    /*
    if (timeInfo.tm_hour == 6 || timeInfo.tm_hour == 12 || timeInfo.tm_hour == 18 || timeInfo.tm_hour == 0){
      // 特定の時刻（6時1分、12時1分、18時1分、0時1分）のみ
      send_line_notify(NotifyMessage); // Line Notifyを送信する
    }
    */
    send_line_notify(NotifyMessage); // Line Notifyを送信する
    
    wifi_disconnect();  // WiFi disconnect
    delay(1000);        // 1秒待ち
  
    // DEEP SLEEPモードに移行
    const uint32_t DEEP_SLEEP_uS = 1000*1000*60*59;    // 59分
    M5.Axp.SetSleep();                                 // Display OFF
    esp_deep_sleep_enable_timer_wakeup(DEEP_SLEEP_uS); // 起動するまでの時間をセット
    esp_deep_sleep_start();                            // DEEP SLEEP 開始
  }
}

void wifi_connect() {
  M5.Lcd.print("WiFi connecting");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { // １秒おきに接続状態を確認
    delay(1000);
    M5.Lcd.print(".");
  }
  M5.Lcd.println("\nconnected");
}

void send_line_notify(String message) {
  const char* host  = "notify-api.line.me";
  const char* token = "xxxxxxxxxxxxxxxxxx"; // LINE Notify のトークンを設定
  
  WiFiClientSecure client;
  M5.Lcd.println("Try connect LINE Notify");
  
  //LineのAPIサーバに接続
  if (!client.connect(host, 443)) { // 443ポート（HTTPS）に接続
    M5.Lcd.println("Connection failed");
    return;
  }
  M5.Lcd.println("Connected");
  
  //リクエストを送信
  String query = String("message=") + message;
  String request = String("") +
               "POST /api/notify HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Authorization: Bearer " + token + "\r\n" +
               "Content-Length: " + String(query.length()) +  "\r\n" + 
               "Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
                query + "\r\n";
  client.print(request);

  //受信終了まで待つ
  while (client.connected()) {
    String line = client.readStringUntil('\n');
    Serial.println(line);
    if (line == "\r") {
      break;
    }
  }

  String line = client.readStringUntil('\n');
  M5.Lcd.println("Send OK");
  delay(1000);
}

void wifi_disconnect() {
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
  M5.Lcd.println("WiFi disconnected");
  delay(1000);
}
```
・2020.9.29追記
　スケッチに不要なコメント行が残っていたため削除しました。
##所感
今回苦労したのは（意外にも）LINE Notifyの通知文でした。通知する文字列に温度の「℃」が出せず、また文字列に湿度の「%」を含むと通知自体が失敗してしまいます。
エスケープシーケンスをいろいろ調べたのですが解決できず、一旦諦めていたのですが、はとね＆きょろさんの記事でご飯の絵文字「🍚」を通知しているのを見つけ、URLエンコーディング（パーセントエンコーディング）を知りました。この方法で文字列を変換することで通知文の問題を解決できました。

ちなみに、このスケッチでは毎時1分に通知を出した後、59分間スリープしています。ほとんど眠っているのですが、それでも内部バッテリーは丸１日は保ちません。最近登場した「M5StickC Plus」は内部バッテリーの容量が増えているのですが、残念ながら2020年9月現在「M5StickC Plus」ではスリープからの復帰がうまく働きません。と、いうわけで今回のスケッチはM5StickC専用となっています。