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

## 目的

前回、[M5StickCを使った玄関の鍵かけ忘れ防止システム(試作)](https://qiita.com/marumen/items/c92dc238f5bef40e4a09) にて作った、鍵かけ忘れ防止システムの以下課題を解決する

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

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

## 環境

+ 利用する部品類、構成は以下

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248487/2a1429ee-ac70-50e2-d827-8989facd8887.png)

+ 実際の設置画像

![image_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248487/b15a77e6-f31d-fe5d-4d60-1a7096bf229d.png)


※参考に今回追加したモバイルバッテリーの詳細を以下に載せておく
　実際に使ったのはこのBTイヤホンの充電用ケース[^1]

[^1]: なぜこんな中途半端なものを使ったかは後述

https://www.amazon.co.jp/%E3%80%90%E6%9C%80%E5%85%88%E7%AB%AFBluetooth5-0-EDR%E3%81%8C%E6%90%AD%E8%BC%89%E3%80%91Bluetooth-3D%E3%82%B9%E3%83%86%E3%83%AC%E3%82%AA%E3%82%B5%E3%82%A6%E3%83%B3%E3%83%89-CVC8-0%E3%83%8E%E3%82%A4%E3%82%BA%E3%82%AD%E3%83%A3%E3%83%B3%E3%82%BB%E3%83%AA%E3%83%B3%E3%82%B0-Android%E5%AF%BE%E5%BF%9C/dp/B07HGXMY2T

## 作業メモ

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

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

磁界センサーデータに`bmm.raw_mag_data.raw_data*` → `bmm.mag_data.*`を使うように変更
raw：無補正データ、新規：補正データで補正データの方が値が小さめで制御しやすかったため、こちらを採用
絶対的な方法ではないが、前回より精度の良い方法として採用

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

<b>参考サイト</b>
https://lang-ship.com/blog/work/m5stickc-power-saving/
https://lang-ship.com/reference/unofficial/M5StickC/Class/AXP192/#getwarningleve
https://msr-r.net/m5stickc-mobilebattery/

具体的に試したのは以下

1. LCD表示明るさ設定(12→8)
1. CPU周波数を落とす(240MHz→80MHz)[^2]
1. 低電圧状態の通知
1. モバイルバッテリーによる給電

[^2]: 今回はWiFi使用するので、試したのは80MHzまで。詳細は[参考サイト](https://lang-ship.com/blog/work/m5stickc-power-saving/)参照

結論としては、上記の1,3,4を採用した。2はCPU周波数を80MHzに落としたところ、`⑤LINEへの鍵状態を通知`が処理速度の問題か届かなかったため、採用せず。

ちなみに、4については[参考サイト](https://msr-r.net/m5stickc-mobilebattery/)に書かれているように、通常のモバイルバッテリーだと、"AUTO POWER OFF"という機能が搭載されており、一定時間給電がないと自動でPower Offしてしまう。
平たく書くと、M5StickCへの給電が小さすぎるため、モバイルバッテリーが勝手にPower Offしてしまうケースがあった。
ただ、今回使用したモバイルバッテリーはなぜか"AUTO POWER OFF"が働かずずっと給電できたため、M5StickCの給電バッテリーとして使うことができた、という流れになる。

### LINE Notify通知画面

![image_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248487/249676e5-8a5a-7c82-8841-68ea6727cc9c.png)


### ソースコード

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

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

```c
#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 180

// 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";

// IFTTTへのデータ送信後の待ち時間
#define WIFI_TRANS_DELAY 3000

// 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;
/* ---- ここまで ---- */

/* ---- 追加分：省電力設定用 ---- */
// 液晶の輝度設定
uint8_t disp_brightness = 8;

// 低電力検知
#define LOW_VOLTAGE 1
uint8_t lowv = 0;
const char *lowv_state[2] = { "Voltage OK", "LOW Voltage" };
/* ---- ここまで ---- */

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;
  }

  // display設定
  M5.Lcd.setRotation(1);
  M5.Lcd.fillScreen(BLACK);
  M5.Axp.ScreenBreath(disp_brightness);

  // CPU周波数設定 → 今回は不採用
  // デフォルトは240MHzで、240, 160, 80, 40, 20, 10から選択可
//  while(!setCpuFrequencyMhz(80)){
//   ;
//  }
  
  // 低電力検知
  lowv = M5.Axp.GetWarningLeve();

  // 低電力の場合、LINE通知(ドア状態は前回値のまま)
  if( lowv == LOW_VOLTAGE ){
    sprintf(valueData, "{\"value1\":\"%s\",\"value2\":\"%s\"}\r\n", door_state[mag_state], lowv_state[lowv]);
    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(WIFI_TRANS_DELAY);
  }
}

void loop() {
  
  // 磁界データ取得
  bmm.read_mag_data();
  magval = (int)(sqrt(pow(bmm.mag_data.x, 2) + pow(bmm.mag_data.y, 2) + pow(bmm.mag_data.z, 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\",\"value2\":\"%s\"}\r\n", door_state[mag_state], lowv_state[lowv]);
      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(WIFI_TRANS_DELAY);

      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.setCursor(0, 0, 2);
    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>連続4日運用中。ドア状態(UNLOCK/LOCK)の判断ミスなし</b>
ということで目的を達成！(まだまだ改善はできるかもしれないが)

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

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