M5StickCを使った玄関の鍵かけ忘れ防止システム(試作)

## 目的
<b>家を出た後に玄関の鍵かけたっけ・・と心配にならないような仕組みを作る</b>
(ただし、いくつか課題があり、題名にも書いてあるように試作レベル。
具体的な課題については最後の[まとめ](#まとめ)で触れる)

## 環境
+ 利用する部品類、構成は以下
![m5stickc_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248487/b7b394e4-92bb-9d9d-36b9-7df352b46ff6.png)

+ 実際の設置画像[^1]

[^1]: 今回は適当だが、実運用する際はもっとケーブル回しをきれいにしたり工夫した方が絶対によい

![m5stickc_8_mozaiku_description_resize.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248487/a353b146-7bde-6a3e-8875-2a7b03f795ff.png)

※参考にセンサー類の詳細を載せておく

+ ENV II Hat

https://www.switch-science.com/catalog/6559/

+ HC-SR501

https://www.amazon.co.jp/Ren-He-HC-SR501%E4%BA%BA%E4%BD%93%E8%B5%A4%E5%A4%96%E7%B7%9A%E6%84%9F%E5%BF%9C%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB-%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E7%84%A6%E9%9B%BB%E5%9E%8B%E8%B5%A4%E5%A4%96%E7%B7%9A%E6%A4%9C%E5%87%BA%E5%99%A8PIR%E3%83%A2%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3-%E3%83%9C%E3%83%87%E3%82%A3%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB/dp/B07DCKZS5S

## 作業メモ

### ※ソースコードは最後に[こちら](#ソースコード)でまとめて紹介します

### 各種センサー類の設定("環境"の図の①～③)

<b>参考サイト</b>
https://lang-ship.com/blog/work/esp32-deep-sleep/
https://github.com/m5stack/M5-ProductExampleCodes/tree/master/Hat/ENVII_HAT/Arduino/ENVII_HAT
https://qiita.com/uniqode/items/1a4fff1e06c8d9958ef2

+ 人感センサーの設定

[HC-SR501のデータシート](https://www.mpja.com/download/31227sc.pdf)などを参考にして、
M5StickCのVout/GND/G33(GPIO端子)を人感センサー側と接続。

+ 人が来たことを検知(人感センサー)してDeep Sleep → wakeup処理

人が近づくと人感センサーからM5StickCのG33(GPIO端子)へHighが入力される構成。
よって[参考サイト](https://lang-ship.com/blog/work/esp32-deep-sleep/) を参考に、
G33(GPIO端子)へのHigh入力をトリガーにDeep Sleep → wakeupする処理を追加。
また、ある程度の時間が経ったら、再度Deep Sleepに戻るようにしている。

+ wakeup中に磁界センサーの値を取得し、その値から鍵の状態を判断

[ENV II Hatのサンプルコード](https://github.com/m5stack/M5-ProductExampleCodes/tree/master/Hat/ENVII_HAT/Arduino/ENVII_HAT)と[ENV II Hatの磁界センサー使い方で参考にしたサイト](https://qiita.com/uniqode/items/1a4fff1e06c8d9958ef2)を参考に、
磁界センサーの値を取得。
`[取得した値]が[閾値]より大きい or 小さい`を確認することで鍵のUNLOCK/LOCKを判断する

### IFTTT/LINE Notifyとの連携("環境"の図の④、⑤)
<b>参考サイト</b>
https://mimimopu.com/raspberry-pi_line/
https://mag.switch-science.com/2016/09/29/ifttt_line-channel/

+ IFTTTとLINE Notifyを連携
ここは[参考サイト](https://mimimopu.com/raspberry-pi_line/)の手順通りにやればできるはず。
今回はメッセージとして送信するValueはValue1しか使っていない。

+ M5StickC → IFTTT → LINE Notifyへ鍵状態を通知
鍵の状態に変化があったら(ex.UNLOCK → LOCKなど)、LINEへ鍵状態を通知する仕組み。
[こちらのサイト](https://mag.switch-science.com/2016/09/29/ifttt_line-channel/)を参考に、M5StickC → IFTTTへトリガーとメッセージ(鍵状態)を送信。
そのトリガーが来ると、先に連携したIFTTT → LINE Notifyへのメッセージ(鍵状態)の送信が発生。

※参考に実際のLINE Notify通知画面
![m5stickc_9_mozaiku_resize_description.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248487/0e09936e-844b-14d7-ab47-5e7a28805337.png)

### ソースコード

<b>設定しないと動かない箇所(WLAN設定[^2]、IFTTTトリガーなど)があるので注意</b>

[^2]: WLANのパスワードをコードにべた書きするのが気になる場合は、[こちらの内容](https://qiita.com/mascii/items/bbee7b7bff9cf5a2f833)を参照にハッシュ化するとよいで

```
#include <M5StickC.h>
#include "SHT3X.h"
#include <Wire.h>
#include "Adafruit_Sensor.h"
#include "bmm150.h"
#include "bmm150_defs.h"
#include <WiFi.h>

/*---------------------------------------------
   Global Data
   ※Globalにする必要がないデータも多数あるが、
   変数の説明のため全てGlobalにまとめた
-----------------------------------------------*/

/* ---- 【要設定】環境依存のパラメータ ---- */
// 磁界センサーの閾値、ドア状態(OPEN/CLOSE)判定用
#define THRESHOLD_VALUE 500

// IFTTT接続用のWLAN設定
// !!!!! 設定しないと動かない !!!!!
#define WLAN_SSID "*****"
#define WLAN_PASS "*****"

// ドア状態の変更を反映させるまでの猶予回数
// ex. CHATTERING_NUM 3 -> 3loop連続"OPEN","OPEN","OPEN"となったらドア状態をOPENに変更する
#define CHATTERING_NUM 3

// IFTTTのトリガー設定
// !!!!! 設定しないと動かない !!!!!
char header[100] = "POST /trigger/****/with/key/**** HTTP/1.1\r\n";

// wakeup → 再度Deep Sleepするまでの時間設定
// 最小時間[ms] = DEEPSLEEP_LOOP * LOOP_DELAY
// この時間を出かける際に玄関から出る → 鍵をかける時間を目安に設定
#define DEEPSLEEP_LOOP 50
#define LOOP_DELAY 200
/* ---- ここまで ---- */

/* ---- [3.磁界センサーからUNLOCK/LOCKを判断]用 ---- */
// 磁界センサー利用
BMM150 bmm = BMM150();

// ドアの状態定義
#define OPEN_STATE  0
#define CLOSE_STATE 1

// Deep Sleep時に値保持のためRTC_DATA_ATTRにて変数定義
RTC_DATA_ATTR int pre_mag_state = OPEN_STATE;
RTC_DATA_ATTR int mag_state = OPEN_STATE;
int magval = 0;
/* ---- ここまで ---- */

/* ---- [4.IFTTTへ鍵の状態(UNLOCK/LOCK)を送信]用 ---- */
// IFTTTへのデータ送信
#define IFTTT_MAX_SIZE_STRING 512
const char *door_state[2] = { "UNLOCK", "LOCK" };

char str[IFTTT_MAX_SIZE_STRING] = {0};
const char* host = "maker.ifttt.com";
char contentLen[50] = {0};
const char* contentType = "Content-Type: application/json\r\n\r\n";
char valueData [150] = {0};
WiFiClient client;
int chattering_count = 0;
/* ---- ここまで ---- */

/* ---- [2.Deep Sleep → wakeup]用 ---- */
// Deep Sleep設定
int deepsleep_count = 0;
/* ---- ここまで ---- */

void setup() {
  M5.begin();
  Wire.begin(0,26);

  if(bmm.initialize() == BMM150_E_ID_NOT_CONFORM) {
#if 0
// for debug
    Serial.println("Chip ID can not read!");
#endif
    while(1);
  } 
  else {
#if 0
// for debug
    Serial.println("Initialize done!");
#endif
  }

  // WiFi Setup
  WiFi.begin(WLAN_SSID, WLAN_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
#if 0
// for debug
    Serial.println("connection failed");
#endif
    return;
  }
}

void loop() {
  
  // 磁界データ取得
  bmm.read_mag_data();
  magval = (int)(sqrt(pow(bmm.raw_mag_data.raw_datax, 2) + pow(bmm.raw_mag_data.raw_datay, 2) + pow(bmm.raw_mag_data.raw_dataz, 2)));

  // ドアopen/close状態更新
  // CLOSE = 閾値より小さい/OPEN = 閾値以上
  if( magval < THRESHOLD_VALUE ){
    mag_state = CLOSE_STATE;
  }
  else{
    mag_state = OPEN_STATE;
  }

  // 状態変化がある場合、LINE通知
  if( pre_mag_state != mag_state ){
    if( chattering_count < CHATTERING_NUM ){
      chattering_count++;
#if 0
      // for debug
      M5.Lcd.setCursor(0, 0, 2);
      M5.Lcd.printf("chattering_count: %d", chattering_count);
      Serial.print("chattering_count: ");
      Serial.println(chattering_count);
#endif
    }
    else{
      sprintf(valueData, "{\"value1\":\"%s\"}\r\n", door_state[mag_state]);
      sprintf(contentLen, "Content-Length: %d\r\n", strlen(valueData));
      sprintf(str, "%sHost: %s\r\n%s%s%s", header, host, contentLen, contentType, valueData);
      client.print(str);
      delay(1000);

      pre_mag_state = mag_state;
      chattering_count = 0;
    }
  }
  // 状態変化がない場合はcountを初期化
  else{
    chattering_count = 0;
  }

  // Deep Sleepするまでのloopカウント
  if( deepsleep_count < DEEPSLEEP_LOOP ){
    deepsleep_count++;
  }
  // 人を検知するまでDeep Sleep
  else{
    // GPIO33がHIGHになったら起動
    pinMode(GPIO_NUM_33, INPUT_PULLDOWN);
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, HIGH);

//  #if 0
    // for debug
    M5.Lcd.printf("Deep Sleep.");
//  #endif

    deepsleep_count = 0;
    // ディープスリープ
    esp_deep_sleep_start();
  }

  delay(LOOP_DELAY);

#if 0
// for debug
  M5.Lcd.setCursor(0, 20, 2);
  M5.Lcd.printf("magval: %05d", magval);
  Serial.print("magval: ");
  Serial.println(magval);

  M5.Lcd.setCursor(0, 40, 2);
  M5.Lcd.printf("mag_state: %s", mag_state);
  Serial.print("mag_state: ");
  Serial.println(mag_state);
#endif

}
```

## まとめ
目的を達成！(試作という意味で。まだまだ課題は残っている)
><b>家を出た後に玄関の鍵かけたっけ・・と心配にならないような仕組みを作る</b>

<b>課題</b>

+ 精度のいいUNLOCK/LOCKの判断方法確立

今回は磁界センサーで判断しているが、磁石やM5StickCの位置がずれると精度が落ちる。
(実際試した際も反応が悪く鍵状態が通知されないことがたまにあった)
そのため、別の方法での検知や磁界センサーの使い方見直し[^3]といった検討が必要。

[^3]: x,y,z軸の値をうまいこと制御するとかでできる？この辺詳しくないためあまりいい方法が思い浮かばず

+ システムの長期稼働方法検討

M5StickCはDeep Sleepさせているとはいえ、長期稼働を考えると電源供給をしないといけない。
ちゃんとした運用をするならば、ここの検討は避けられない。