M5Stackで距離監視アラートを作ってみた。

#背景
現在4カ月の子どもの育児に奮闘中です。子どもを抱っこしたまま玄関で消毒液を手に噴霧したいという要望があり、子どもが生まれる前にノリで買ったM5stackが使えるのではと考え、トライすることとしました。M5stackのプログラミングはほぼ初心者なので、今回は測距センサユニットの使い方に慣れることを目的に、測距センサユニットを使った簡単な応用例として、対象物との距離監視アラートを作りました。

#実現したいこと
M5stackで測距センサユニットから取得した距離が事前に設定した距離(閾値)よりも小さい(対象物が近づいてきている)場合に、M5stackからアラートを鳴らす。問題なければ何もならさない。できれば閾値はM5stack側で調整したい。

#用意するもの
[M5stack Basic](https://www.switch-science.com/catalog/3647/)
[M5stack ToF測距センサユニット](https://www.switch-science.com/catalog/5219/):センサ部はVL53L0X


#距離の測り方(ToF測距センサユニットの使い方)
M5stackと測距センサユニットを用いて測距センサユニットに距離を表示します。まずM5stackと測距センサユニットを付属のGroveケーブルを用いてつなぎます。続いて、PCとM5stackをつないでArduinoIDEにてM5stack内での処理を記述していきます。M5stackのライブラリがインストールされていればArduinoIDEにて
ファイル>スケッチ例>M5stack>Unit>VL53L0X
に今回用いるセンサの距離ユニットの測定結果を表示するサンプルコードがあります。ただし、内容が若干込み入っておりもう少しシンプルに使いたいので、今回は別途ライブラリを使っていきます。
##測距センサユニット(VL53L0X)のライブラリ
[VL53L0XをM5Stackで使う](https://qiita.com/nnn112358/items/1d71c0b3269ee977e781)を参考にさせていただき、VX53L0Xのライブラリを導入します。"VL53L0X by Pololu"となっているものを導入しましょう。このセンサおよびライブラリでの動作テストを以下の通り実行します。

```cpp:minimal_VL53L0X.ino
#include <M5Stack.h>
#include <Wire.h>
#include <VL53L0X.h>

VL53L0X sensor; //VL53L0X　"sensor"として操作することを宣言している。

void setup() {
  M5.begin();
  M5.Power.begin();
  Wire.begin();// I2C通信を開始する
  sensor.init();
  sensor.startContinuous();  //  連続測定を開始
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(10, 10);
  M5.Lcd.setTextColor(WHITE, BLACK);
  M5.Lcd.setTextSize(4);
}
void loop() {
  int distance = sensor.readRangeContinuousMillimeters(); //センサデータの取得(mm単位)
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.print(distance);
  M5.Lcd.print("  [mm]  ");

}
```
これをM5stackへコンパイルすると、ものすごい勢いでデータが更新されます。このままでは、実際の使用やデバッグにはあまり向かないかと思います。そこで、[スイッチサイエンス様](http://pages.switch-science.com/letsiot/ranging/)のサイトを参考に、10点程度の平均値を表示値として返すようにします。ついでにセンサ側に何らかの問題があった場合にそれが分かるように、タイムアウトなどの設定も入れておきます。

```cpp:VL53L0X_show_sensor_value.ino
#include <M5Stack.h>
#include <Wire.h>
#include <VL53L0X.h>

VL53L0X sensor; //VL53L0X　"sensor"として操作することを宣言している。

void setup() {
  M5.begin();
  M5.Power.begin();
  Wire.begin();// I2C通信を開始する
  sensor.init();
  sensor.setTimeout(500); //　タイムアウトの時刻を設定(500 ms = 0.5 sec)
  sensor.startContinuous();  //  連続測定を開始
  M5.Lcd.fillScreen(BLACK);  //以降表示周りの初期設定
  M5.Lcd.setCursor(10, 10);
  M5.Lcd.setTextColor(WHITE, BLACK);
  M5.Lcd.setTextSize(4);
}
void loop() {
  int VL53L0X_d = 0; // センサ値の和をいったん0とする
// 0.1 sec 刻みでセンサ値を測定、和を計算する
  for (int i = 0; i < 10; i++) {
     VL53L0X_d += sensor.readRangeContinuousMillimeters();
     delay(100);
  }
// 10個のセンサ値の和から平均を算出
  int distance = VL53L0X_d/10;
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.printf("D: %d  [mm]  ",distance); //距離表示形式を調整
#タイムアウトが発生すればその旨表示
  if (sensor.timeoutOccurred()) {
    M5.Lcd.setCursor(0, 50);
    M5.Lcd.println("TIMEOUT");
#タイムアウトがなければ表示なし。
  }else{
    M5.Lcd.setCursor(0, 50);
    M5.Lcd.println("        ");  
  }
}
```
センサからのデータ取得等の設定はいったんこれで十分でしょう。実際にアラートとするときに細かい部分は調整していきます。

#閾値の設定と変更
閾値の設定もM5stack内で完結させたいので、この部分の処理を考えていきます。具体的には、いったん適当な距離を設定しておいて、M5stackのボタンを押すことで、これらの設定値が変化していくように作っておきます。M5Stackのボタンは左からA,B,Cと対応付けされており、これらが押し込まれた際に閾値をそれぞれ、+10,+1,-10とするように処理を加えていきます。ボタンの挙動については[こちらのサイト様](https://m5stack-build.hatenablog.com/entry/2019/07/27/093429)が参考になります。

```cpp:VL53L0X_show_sensor_value_threshold.ino
#include <M5Stack.h>
#include <Wire.h>
#include <VL53L0X.h>

VL53L0X sensor; //VL53L0X　"sensor"として操作することを宣言している。
int thre = 100; //閾値の設定

void setup() {
  M5.begin();
  M5.Power.begin();
  Wire.begin();// I2C通信を開始する
  sensor.init();
  sensor.setTimeout(500);
  sensor.startContinuous();  //  連続測定を開始
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(10, 10);
  M5.Lcd.setTextColor(WHITE, BLACK);
}

void loop() {
  M5.update();//ボタンの処理を実行する際に必要
  M5.Lcd.setTextSize(4);
  int VL53L0X_d = 0;
  for (int i = 0; i < 10; i++) {
     VL53L0X_d += sensor.readRangeContinuousMillimeters();
     delay(100); //0.1秒間停止
  }
  int distance = VL53L0X_d/10;
  if(thre>distance){
      M5.Lcd.setTextColor(RED, BLACK); //閾値以下の値となった場合には文字表示を赤色にする
    }
  M5.Lcd.setCursor(0, 50); //距離表示の位置を調整
  M5.Lcd.printf("D: %d  [mm]  ",distance);  
  //タイムアウトとなった場合の処理を記述
  M5.Lcd.setTextColor(WHITE, BLACK);
  if (sensor.timeoutOccurred()) {
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.println("TIMEOUT");
  }else{
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.println("        ");  
  }
    //以下にボタンを押し込まれた際の処理を記述
  //Aボタン　閾値に10を加える。
  if(M5.BtnA.isPressed()){
    thre += 10;
  }
  //Bボタン　閾値に1を加える。
  if(M5.BtnB.isPressed()){
    thre += 1;
  }
  //Cボタン　閾値から10引く。
  if(M5.BtnC.isPressed()){
    thre -= 10;
  }
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(50, 150);
  M5.Lcd.printf("Threshold: %d [mm] ", thre);//閾値の表示
}
```
ちょこちょこと表示関係をいじっております。同時に閾値より対象物が近くに来た場合に表示色が赤くなるように設定しました。

#アラートを鳴らす
アラートというのですから音を鳴らさないといけません。M5stackの音の出力周りはいろいろ情報が混在しております。初期は音が大きすぎたり、コントロールが難しいといった問題があったようですが、最近この辺りが改善され、割と簡単に音出力が設定できるようになりました。[MSR合同会社様の記事](https://msr-r.net/m5stack-sound/#:~:text=A%E3%83%9C%E3%82%BF%E3%83%B3%E3%82%92%E6%8A%BC%E3%81%99%E3%81%A8,%E9%9F%B3%E3%81%8C%E9%B3%B4%E3%82%89%E3%81%AA%E3%81%8F%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82)を参考に、アラートの発報を導入してみましょう。

```cpp:VL53L0X_alart.ino
#include <M5Stack.h>
#include <Wire.h>
#include <VL53L0X.h>

VL53L0X sensor; //VL53L0X　"sensor"として操作することを宣言している。
int thre = 100; //閾値の設定
int volume = 1; //音量の設定の最低値だが、結構大きな音がなるので注意。

void setup() {
  M5.begin();
  M5.Power.begin();
  Wire.begin();// I2C通信を開始する
  sensor.init();
  sensor.setTimeout(500);
  sensor.startContinuous();  //  連続測定を開始
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(10, 10);
  M5.Lcd.setTextColor(WHITE, BLACK);
  M5.Speaker.setVolume(volume);
}

void loop() {
  M5.update();//ボタンの処理を実行する際に必要
  M5.Lcd.setTextSize(4);
  int VL53L0X_d = 0;
  for (int i = 0; i < 10; i++) {
     VL53L0X_d += sensor.readRangeContinuousMillimeters();
     delay(100); //0.1秒間停止
  }
  int distance = VL53L0X_d/10;
  if(thre>distance){
      M5.Lcd.setTextColor(RED, BLACK); //閾値以下の値となった場合には文字表示を赤色にする
      M5.Speaker.tone(440,100); //音色の設定(第一引数)と鳴らす時間の設定(ms)(第二引数)
    }
  M5.Lcd.setCursor(0, 50); //距離表示の位置を調整
  M5.Lcd.printf("D: %d  [mm]  ",distance);  
  //タイムアウトとなった場合の処理を記述
  M5.Lcd.setTextColor(WHITE, BLACK);
  if (sensor.timeoutOccurred()) {
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.println("TIMEOUT");
  }else{
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.println("        ");  
  }
    //以下にボタンを押し込まれた際の処理を記述
  //Aボタン　閾値に10を加える。
  if(M5.BtnA.isPressed()){
    thre += 10;
  }
  //Bボタン　閾値に1を加える。
  if(M5.BtnB.isPressed()){
    thre += 1;
  }
  //Cボタン　閾値から10引く。
  if(M5.BtnC.isPressed()){
    thre -= 10;
  }
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(50, 150);
  M5.Lcd.printf("Threshold: %d [mm] ", thre);//閾値の表示
}
```
実際には一つ前のコードに対して、Volumeの初期設定とVolumeのセットアップ、距離に応じて音を鳴らす部分の三行を追加しただけです。音色(音の高さ)は、M5.Speaker.toneの第一引数、音の長さ(省略可)はM5.Speaker.toneの第二引数で変更可能です。

#実験
コップにお酒を注がれすぎないように液面の監視をしましょう。
![PXL_20210131_112822225.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/714168/f69749fd-e67a-426b-64fe-dfcdc9a8ecc4.jpeg)
空っぽの状態では43mmの表示です。ここにお酒を注がれると、
![PXL_20210131_112901028.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/714168/bf0b61b8-29cc-c323-a79e-6201723d08ab.jpeg)
液面が上がってきたので、液面とセンサ部との距離が小さくなり、赤色の表示になりました。静止画ではわかりませんがきちんと音が鳴っています。
やってみるとわかりますが、実際に固体の物体を相手にする場合と比較して変に低い数値が出たりとセンサ値の安定性に若干不安があります。VL53L0Xは光の反射を利用して距離を測っているので水のような透明な物体はやや苦手なのかもしれません。改善策としては[超音波式の測距センサユニット](https://www.switch-science.com/catalog/6738/)を用いる、などがあるかと思います。今回は、消毒液自動噴霧器の作成が最終目的なのでこの辺りには深入りしないこととします。

#まとめ
M5stackと測距センサユニット(VL53L0X)を用いて距離監視アラートを作ってみました。距離の閾値はM5stackから操作でき、閾値よりも近い距離に対象物が来た際には画面表示が赤くなり、音を発して周りに知らせます。次回は、サーボモーターの取り扱いについて試してみるとともに消毒液自動噴霧器の作成を進めていきます。

#参考サイト
[AmbientでIoTをはじめよう-M5Stackで距離を測る-](http://pages.switch-science.com/letsiot/ranging/)
[VL53L0XをM5Stackで使う](https://qiita.com/nnn112358/items/1d71c0b3269ee977e781)
[とある科学の備忘録 【M5Stack】第7回 ToFセンサを使って物体との距離[mm]を計測](https://shizenkarasuzon.hatenablog.com/entry/2020/05/21/013506)
[m5stackで組み込み!! -ボタンを使ってカウンタを作ってみた-](https://m5stack-build.hatenablog.com/entry/2019/07/27/093429)
[MSR合同会社　-M5Stackでできること 〜内蔵スピーカーで音を鳴らす-](https://msr-r.net/m5stack-sound/#:~:text=A%E3%83%9C%E3%82%BF%E3%83%B3%E3%82%92%E6%8A%BC%E3%81%99%E3%81%A8,%E9%9F%B3%E3%81%8C%E9%B3%B4%E3%82%89%E3%81%AA%E3%81%8F%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82)