【M5Stack】マイコンのボタンをトリガーにしてLINE Notifyに通知を送る

# きっかけ
自室が二階にあるのですが、一階から家族に呼ばれても基本的に声が聞こえず。LINEを通じて携帯とパソコンに通知がいくようにすれば自分も気がつくのでは？と思ったから。いつもイヤホンをして作業をしているので、声をかけられても気がつかないのだ。

# 使用したもの と 環境
- `MacBook Pro 2019 13inch macOS Catalina 10.15.4`
- `M5Stack Basic`
- `IFTTT`
- `Arduino IDE v1.8.12` 

# 手順
<ol>
<li>Arduino IDEのダウンロード & M5Stack開発環境セットアップ</li>
<li>IFTTTのアカウント登録 & T(his)とT(hat)の設定</li>
<li>コードを書いていこう</li>
<li>(時間があったので)画面表示ををいい感じに</li>
</ol>

# やっていこう

## §1 Arduino IDEインストール & M5Stack開発環境セットアップ
こちら( https://qiita.com/hmmrjn/items/2b2da09eecffcbdbad85 )のサイトを参考に。

先人の皆さんいつもありがとうございます。


まずはArduino IDEをインストール( https://www.arduino.cc/en/main/software )。
Mac OS Xのものを入手して、zip解凍、アプリケーションフォルダへドラッグアンドドロップ。

（上記サイトではすごい丁寧に説明されています。私の場合はドライバインストールせずともM5Stack認識しました。）

## §2 IFTTTのアカウント登録 & T(his)とT(hat)の設定
こちら( http://kyokucho1989.chillout.jp/2019/11/09/ifttt%E3%81%A7%E7%B0%A1%E5%8D%98iot%EF%BC%81m5stack%E3%81%A8line%E3%82%92%E9%80%A3%E6%90%BA%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/ )のサイトを参考に。


## §3 コードを書いていこう
§2でみたサイトにコード例が表示されているので、まずはそのままコピーアンドペーストして動作を確認してみてください。

ただし、一点、注意です。

こちらのサイトのコードのsetup()に関して

```
void setup() {
  //Initialize serial and wait for port to open:
  Serial.begin(115200);
  delay(100);

  M5.begin()     <- これを追加
 
  WiFi.begin(ssid, password);
  while (!checkWifiConnected()) {
    WiFi.begin(ssid, password);
  }
}
```
こうしてあげないと、いくらM5.Lcd.print()しても画面表示が現れません。

もし一番左以外のボタンの動作を追加するのであればloop()内で

```
if (M5.BtnA.wasReleased()) {
   send("Abtn","test1","test2");  //ボタンAのメッセージ
   M5.Lcd.println("send");
}
if (M5.BtnB.wasReleased()) {
   send("Bbtn","test1","test2");  //ボタンBのメッセージ
   M5.Lcd.println("send");
}
if (M5.BtnC.wasReleased()) {
   send("Cbtn","test1","test2");  //ボタンCのメッセージ
   M5.Lcd.println("send");
}
```
のようにして動作を追加していってもOK

sendの引数に渡す文字列にスペースを入れると動かないのも注意。

今回参考にしたサイトの方式ですと特定のURLにHTTP/GETメソッドでアクセスしている、すなわちクエリストリングの形でcontextを送信するためスペースが入っているとurlが切れてしまいます。

POSTで送信することも原理的には可能と思われます。きちんとリクエスト文さえ組み立てれば言い訳です。ここ( https://amg-solution.jp/blog/14267 )を参考にしてやって私も次はPOSTで組み立ててみたいかも。


##§4 画面表示をいい感じに

やっぱりそのままの画面だとなんとなく面白くないので

```
M5.Lcd.clear
M5.Lcd.setTextColor
M5.Lcd.setTextSize
M5.Lcd.setCursor
M5.Lcd.fillRoundRect
M5.Lcd.drawLine
```
などを使って仕上げましょう。


それ以外には、せっかくWifiに接続しているので、ぜひ時刻を取得して表示させることもできます。

ここ( https://m5stack-build.hatenablog.com/entry/2019/09/18/074659 )を参考にして見るといいと思います。

§2で示されていたコードのloopメソッド内にそのままprintLocalTime()で時間を表示させようとすると、一世代前のブラウン管のような走査線の入ったような表示になってしまいます。delayの引数を20より大きな値に変更しましょう。単位はmsなので1000だと1秒になります。1秒毎に表示を変更させる設定にすると見栄え良くなります。
ちなみにdelayの値を大きくするほどボタンの反応が悪くなるので解決方法はdelayの値をチューニングして反応を確かめる他、画面更新処理のみ別スレッドで行う( https://qiita.com/nnn112358/items/733b5f52957980bb9fb1 )ことも一つ案としてあります。これも次はチャレンジしてみたい。

では以下コードです。たった一日で遊びながら食べながら書いていますので汚いのはお許しくださいませ。

```C++
#include <M5Stack.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include "time.h"

#define FALSE 0
#define TRUE 1

// LCD関連
#define HIGHT_MAX 240
#define WIDTH_MAX 320

// WIFI設定
const char* ssid     = "{{ ssid }}";
const char* password = "{{ password }}";

// IFTTT関連の設定
String makerEvent = "{{ XXXXXXXX }}";   // Maker Webhooks
String makerKey = "{{ XXXXXXXXX }}"; // Maker Webhooks
const char* server = "maker.ifttt.com";  // Server URL
WiFiClient client;

// 時刻表示に関する設定
const char* ntpServer = "ntp.nict.jp";
const long  gmtOffset_sec = 3600 * 9;
const int   daylightOffset_sec = 0;


void setup() {
  // Initialize serial and wait for port to open:
  Serial.begin(115200);
  delay(100);

  // LCD setup
  M5.begin();

  // Wifi setup
  WiFi.begin(ssid, password);
  while (!checkWifiConnected()) {
    WiFi.begin(ssid, password);
  }
  
  // サーバーから時刻情報を取得
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}

void loop() {
  M5.update(); //これをしないと画面が更新されない
  M5.Lcd.clear(BLACK);
  
  printButtonTreat(3, "A", "B", "", 20);  // 画面下のボタンの詳細をを表示するメソッド
  printLocalTime();  // 画面中央の時刻表示するメソッド
    
  if (M5.BtnA.isPressed()) { // isPressedにすることでボタンの反応をよくした
     send("Notify","Dinner_Time", ""); //任意の文字列3つ
     
     M5.Lcd.clear(BLACK);
     M5.Lcd.setCursor(60,65);
     M5.Lcd.println("send");
     delay(500);
   
  }else if (M5.BtnB.isPressed()) {
     send("Hello","", ""); //任意の文字列3つ
     
     M5.Lcd.clear(BLACK);
     M5.Lcd.setCursor(60,65);
     M5.Lcd.println("send");
     delay(500);
  }
  delay(1000);
  
}



bool checkWifiConnected() {
  // attempt to connect to Wifi network:
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    // wait 1 second for re-trying
    delay(1000);
  }

  Serial.print("Connected to ");
  Serial.println(ssid);
  return true;
}

void send(String value1, String value2, String value3) {
  while (!checkWifiConnected()) {
    Serial.print("Attempting to connect to WiFi");
    WiFi.begin(ssid, password);
  }

  Serial.println("\nStarting connection to server...");
  if (!client.connect(server, 80)) {
    Serial.println("Connection failed!");
  } else {
    Serial.println("Connected to server!");
    // Make a HTTP request:
    String url = "/trigger/" + makerEvent + "/with/key/" + makerKey;
    url += "?value1=" + value1 + "&value2=" + value2 + "&value3=" + value3;
    client.println("GET " + url + " HTTP/1.1");
    client.print("Host: ");
    client.println(server);
    client.println("Connection: close");
    client.println();
    Serial.print("Waiting for response "); //WiFiClientSecure uses a non blocking implementation

    int count = 0;
    while (!client.available()) {
      delay(50); //
      Serial.print(".");
    }
    // if there are incoming bytes available
    // from the server, read them and print them:
    while (client.available()) {
      char c = client.read();
      Serial.write(c);
    }

    // if the server's disconnected, stop the client:
    if (!client.connected()) {
      Serial.println();
      Serial.println("disconnecting from server.");
      client.stop();
    }
  }
}


void printLocalTime()
{
  struct tm timeinfo;
  
  if(!getLocalTime(&timeinfo)){
    M5.Lcd.println("Failed to obtain time");
    return;
  }
  // テキストサイズ指定
  M5.Lcd.setTextSize(4);
  // カーソル位置を設定
  M5.Lcd.setCursor(45,65);
  M5.Lcd.printf("%04d-%02d-%02d" 
                ,timeinfo.tm_year + 1900
                ,timeinfo.tm_mon
                ,timeinfo.tm_mday
                );

  M5.Lcd.setTextSize(5);
  M5.Lcd.setCursor(45,100);
  M5.Lcd.printf("%02d:%02d:%02d"
                ,timeinfo.tm_hour
                ,timeinfo.tm_min
                ,timeinfo.tm_sec
                );
}

// ここから下はあまり関係ないですが一応載せます、正直数字をひたすらいじってるだけで正直参考になりません
int btnA_pos[] = {(WIDTH_MAX / 8), HIGHT_MAX}; //左のボタンの表示位置
int btnB_pos[] = {((WIDTH_MAX * 2) / 5), HIGHT_MAX}; //中央ボタンの表示位置
int btnC_pos[] = {((WIDTH_MAX * 5) / 7), HIGHT_MAX}; //右のボタンの表示位置

// 画面下部にボタンの説明を表示させるメソッド
void printButtonTreat(int s, String a, String b, String c, int diff){
    // 青色の奥行きを感じさせるような線を表示させています
    int k;
    for(k = 11; k > 0; k--){
      M5.Lcd.drawLine(WIDTH_MAX / 2 + 10 + 12 * k, HIGHT_MAX / 2 + 65 - k * 2, WIDTH_MAX / 2 - 10 - 12 * k,  HIGHT_MAX / 2 + 65 - k * 2, getColor(0, 0, 255 - k * 20));
    }

    //ボタンA上部の文字
    M5.Lcd.setCursor(btnA_pos[0] + diff, btnA_pos[1] - (s * 10) );
    M5.Lcd.setTextSize(s);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.printf("%s", a);
    M5.Lcd.drawRoundRect(btnA_pos[0] + diff - 32, btnA_pos[1] - (s * 10) - 5, 80, 30, 8, WHITE);

    //ボタンB上部の文字
    M5.Lcd.setCursor(btnB_pos[0] + diff, btnB_pos[1] - (s * 10));
    M5.Lcd.setTextSize(s);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.printf("%s", b);
    M5.Lcd.drawRoundRect(btnB_pos[0] + diff - 25, btnB_pos[1] - (s * 10) - 5, 80, 30, 8, WHITE);

    //ボタンC上部の文字
    M5.Lcd.setCursor(btnC_pos[0] + diff, btnC_pos[1] - (s * 10));
    M5.Lcd.setTextSize(s);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.printf("%s", c);
    M5.Lcd.drawRoundRect(btnC_pos[0] + diff - 32, btnC_pos[1] - (s * 10) - 5, 80, 30, 8, WHITE);
}

// RGBを16進数表示にするメソッド
uint16_t getColor(uint8_t red, uint8_t green, uint8_t blue){
  return ((red >> 3)<<11) | ((green >> 2) << 5) | (blue >> 3);
}
```

動作デモ動画をみてみたい場合はここ( https://youtu.be/kAosDaD5lL4 )でご覧ください。


# おわり
LINEと連携させるのがこんなに簡単にできるなんて思わなかったのでびっくり。

IFTTTが面白そうなので、他にもいろんなことに使ってみようと思います。