M5Stackで雨の降り始めを通知してみる

## はじめに
最近突然の雨で、外に干していた洗濯物が濡れてしまうことが続いていたので、
M5Stackを使って、雨の降り始めを通知するアプリを作ってみようと思います。

### 困ってたこと

- 自分が住んでいる場所の天気が必ずしも天気予報と同じ天気ではなかったため、よく雨で洗濯物が濡れてしまっていた
- 雨が降っていないか確認するのがめんどくさかった

### 今回作るもの

- すぐに洗濯物を取り込めるように、雨の降り始めのタイミングでSlackで通知してくれる
- 手元にあったM5Stack, 水分センサー(本来は土中の水分量を計測するもの), M5Stack 3G拡張ボードを使って試してみる
- Wifi利用、３G回線利用のそれぞれで試してみる


## 開発環境、使用したもの

- Mac
- [M5Stack Gray](https://www.switch-science.com/catalog/3648/)
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704621/649fc268-a07d-6589-3737-5cf3943d6c05.jpeg">

- [M5Stack用 3G 拡張ボード](https://soracom.jp/products/kit/3g_module_m5stack/)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704621/c2e1855e-eddc-38ee-70ac-e1caa57f3d84.png" width="500">

- [Grove 水分センサー](https://jp.seeedstudio.com/Grove-Moisture-Sensor.html)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704621/c94f1d4f-1f3e-17b7-a29d-49561bcf5c95.jpeg" width="300">

## 手順

### 手順１： M5StackをMacで開発するための準備

今回は、最近PCを新しくしたこともあり、Arduino IDEのインストールから必要でしたので、以下の手順で進めました。

1. MacにArduino IDEをインストールする
    - https://www.arduino.cc/en/Main/Software を開き、Mac用をダウンロード&インストールする
1. ESP32用のボードマネージャを追加する
    - Arduino > Preferences > 追加のボードマネージャのURLに以下のURLを追加します
      `https://dl.espressif.com/dl/package_esp32_index.json`
1. M5Stack用のライブラリをインストールする
    - スケッチ > ライブラリをインクルード > ライブラリを管理 > 検索フィルタに`M5Stack`と入力し、M5Stackライブラリをインストールする
    
        <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704621/45e31dd8-5cc5-7ce9-bae5-a70500af2dfc.png" width="400">


1. MacとM5Stackを接続した後、ボード、シリアルポート等の設定値を以下のように設定します
    - 設定値
    
        <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704621/6d13f857-08a8-8bfe-e9d7-4ae1ccf4dce4.png" width="200">

1. サンプルスケッチを書き込み、動作確認をします
    - ファイル > スケッチ例 > M5Stack > Basics > Button を選択し、サンプルスケッチを開き、M5Stackに書き込みます。
    - 書き込みが成功すれば、M5Stackのディスプレイに以下のように表示されているはずです。（写真はボタンを左から一回ずつ押した後の状態です）

        <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704621/3975ec29-58a9-3d52-2c3b-c6cb4a2ed1e2.png" width="300">

### 手順２： 水分量をM5Stackで測定する

手元にあった水分量センサーをM5Stackとつなげ、以下のサンプルコードを書き込み、センサーが濡れている時は1, それ以外は0が取得できることを確認します。

- サンプルコード

```
#include <M5Stack.h>
#define WATER_SENSOR 36

void setup() {
  M5.begin();
  pinMode(WATER_SENSOR, INPUT);
}

void loop() {
  Serial.println(digitalRead(WATER_SENSOR));
  M5.Lcd.println(digitalRead(WATER_SENSOR));
  delay(1000);
}
```

- 結果。センサーを水につけている時だけ1が出力されることが確認できました。

    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704621/f855aa9c-6eb7-c6db-9449-e5153ea05870.png" width="300">

### 手順3: 水分が検出できた時にSlackに通知する

Slack通知には、Incoming WebHooksを利用します。
下記のサンプルコードで、Slack通知することができるはずです。
この例では、Wifi通信を利用しています。


```
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <M5Stack.h>
 
#define WIFI_SSID "{SSID}"
#define WIFI_PASS "{PASSWORD}"
#define WATER_SENSOR 36
const char *server = "hooks.slack.com";
const char *json = "{'channel': '{チャンネル名}', 'username': 'てるてる坊主', 'text': '雨が降り始めたよ！', 'icon_emoji': ':rain_cloud:'}";
const char* slack_root_ca= "証明書" ;
WiFiMulti wifi;
HTTPClient http;

int isDetected = false;

void setup() {
  Serial.begin(115200);
  M5.begin();
  wifi.addAP(WIFI_SSID, WIFI_PASS);
  while (wifi.run() != WL_CONNECTED) { delay(500); }
  pinMode(WATER_SENSOR, INPUT);
  M5.Lcd.clear(BLACK);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(20);
  M5.Lcd.println();
  M5.Lcd.print(" Sunny!");
}

void loop() {
  if (digitalRead(WATER_SENSOR) == 1 && isDetected == false) {
    postSlack();
    isDetected = true;
    M5.Lcd.clear(BLACK);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.println();
    M5.Lcd.print(" Rainy!");
  }
 delay(1000 * 10); 
}

void postSlack() {
  http.begin(server, 443, "/services/XXX/YYY/ZZZ", slack_root_ca);
  http.addHeader("Content-Type", "application/json" );
  http.POST((uint8_t*)json, strlen(json));
}

```

### (オプション)手順４： M5Stack３G拡張モジュールを利用したSlack通知を試してみる

M5Stackには元々WiFiモジュールが組み込まれているため、３G拡張モジュールを利用する必要はありませんが、
Wifiでの通信を３G通信に置き換えたSlack通知も試してみたので、そちらについてもメモをしておきます。
デバイス側の負荷軽減という観点では、こちらの方が好ましいかもしれません。

必要なものは、M5Stack３G拡張モジュールとSIMカードです。
今回は、Soracom Beamを経由して、Slackに通知する構成で実現してみました。

#### セットアップ
- M5Stack 3G拡張モジュールのセットアップやライブラリのインストールは、[こちら](https://dev.soracom.io/jp/start/m5stack/)を参考にしました。

#### HTTPアクセスの動作確認

- まずは3GでのHTTPアクセスの動作確認をしたかったので、
`ステップ 2: M5Stack で開発してみる (HTTP アクセス)` のスケッチをそのまま利用することにしました。
このスケッチでは、TinyGsmClientを利用しているので、M5Stack用のライブラリをインストールした時と同様の手順で、TinyGsmClientについてもインストールが必要になります。
- HTTPアクセスに成功すると、M5Stackのディスプレイ上に、HTTPアクセス時の各種情報が表示されます。

#### デバイス側サンプルコード

動作確認ができたら、Slack通知にとりかかります。
M5Stack側からSoracom Beamに連携するコードは下記のコードが参考になると思います。
(※このサンプルでは水分センサーの処理は入れていません。)

```
#include <M5Stack.h>

#define TINY_GSM_MODEM_UBLOX
#include <TinyGsmClient.h>

TinyGsm modem(Serial2); /* 3G board modem */
TinyGsmClient ctx(modem);

void setup() {
  Serial.begin(115200);
  M5.begin();
  M5.Lcd.clear(BLACK);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.println(F("M5Stack + 3G Module"));

  M5.Lcd.print(F("modem.restart()"));
  Serial2.begin(115200, SERIAL_8N1, 16, 17);
  modem.restart();
  M5.Lcd.println(F("done"));

  M5.Lcd.print(F("getModemInfo:"));
  String modemInfo = modem.getModemInfo();
  M5.Lcd.println(modemInfo);

  M5.Lcd.print(F("waitForNetwork()"));
  while (!modem.waitForNetwork()) M5.Lcd.print(".");
  M5.Lcd.println(F("Ok"));

  M5.Lcd.print(F("gprsConnect(soracom.io)"));
  modem.gprsConnect("soracom.io", "sora", "sora");
  M5.Lcd.println(F("done"));

  M5.Lcd.print(F("isNetworkConnected()"));
  while (!modem.isNetworkConnected()) M5.Lcd.print(".");
  M5.Lcd.println(F("Ok"));

  M5.Lcd.print(F("My IP addr: "));
  IPAddress ipaddr = modem.localIP();
  M5.Lcd.print(ipaddr);
  delay(2000);
}

void loop() {
  M5.update();

  M5.Lcd.clear(BLACK);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println(F("Post to SORACOM BEAM"));

  if (!ctx.connect("uni.soracom.io", 80)) {
    Serial.println(F("Connect failed."));
    return;
  }
  Serial.println(F("connected."));

  /* build payload */
  const char *json = "{'channel': '{チャンネル名}', 'username': 'てるてる坊主', 'text': '雨が降り始めたよ！', 'icon_emoji': ':rain_cloud:'}";
  char payload[1024];
  sprintf(payload, "%s", json);

  /* send request */
  ctx.println("POST / HTTP/1.1");
  ctx.println("Host: uni.soracom.io");
  ctx.println("Content-Type: application/json");

  char content_length_hdr[32];
  sprintf(content_length_hdr, "Content-Length: %lu", strlen(payload));
  ctx.println(content_length_hdr);
  ctx.println();
  ctx.println(payload);
  Serial.println("sent data below;");
  Serial.println(payload);

  /* receive response */
  while (ctx.connected()) {
    String line = ctx.readStringUntil('\n');
    Serial.println(line);
    if (line == "\r") {
      Serial.println("headers received.");
      break;
    }
  }
  char buf[1 * 1024] = {0};
  ctx.readBytes(buf, sizeof(buf)); /* body */
  ctx.stop();
  M5.Lcd.println(buf);

  Serial.println("received data below;");
  Serial.println(buf);

  Serial.println("### Wait.");
  delay(1000 * 10);
}
```

#### Soracom Beam設定

Soracom BeamからSlackのWebhookAPIを実行するには、Soracomコンソール側で設定するだけで実現できます。
よくあるAWS Lambda等のサーバーレスファンクションを構築する必要はありません。

設定に関しては、[SORACOMのドキュメント](https://dev.soracom.io/jp/start/beam/)が参考になりますので、参考にしてみてください。

## デモ

映っていないですが、水分センサーに水滴をたらしたときの、M5Stackの表示とSlack通知結果を貼っておきます。

|M5Stackディスプレイ|Slack通知|
|---|---|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704621/05f6fca1-521e-8c57-42ac-5d26bd272aea.gif" width="200">|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704621/e84621be-0ca0-c71d-6b27-1cfca4ef63dc.png" width="300">|

## 終わりに

今回は手元にあるデバイス、センサーを使って作ってみました。
まだ、実践では試せていないので、次回洗濯物を干す際に試してみようと思います。

FutureWorkとして、時間があれば以下の対応をしてみたいと思っています。

- [電池モジュール](https://www.switch-science.com/catalog/3653/)をStackする
- [水分センサー](https://wiki.seeedstudio.com/Grove-Water_Sensor/)を変更する。持っていたものは、土壌用だったので。
- Slack通知ではなくて、LINE通知に変更する