M5StickC plus を使った工作例 その①~降雪確認装置編~

M5StickCPlus って使ったことありますか?

めまめまはとある講座に参加して初めて使ったのですが、かなり幅広いことに使えて、めっちゃ便利なので今でも遊んでいます。

その中でもめまめまの傑作である"降雪確認装置"を紹介します。

Amazon.co.jp: Plus ESP32-PICOミニIoT開発ボード iotキット フィンガーコンピューターカラーLCD : パソコン・周辺機器

 

 

 

目次----------------------

降雪確認装置の仕組み

降雪確認装置の仕組みは単純です。

図のようにセンサーが反応したら、ラインに通知されるという仕組みです。

 

M5stickCのバッテリーだけだと一時間弱でバッテリーが切れてしまうので、モバイルバッテリーを繋げました。

モバイルバッテリーを繋げることで一週間は充電しなくても動いてくれます。

 

どうやってLineに通知するの?~M5stickCPlusとLine Notifyのつなげ方~

 

装置の仕組みの章でサラッと「ラインに通知する」と言いましたが、M5stickCPlus とLine を繋げるのはとても簡単です。

M5StickCからスマートフォンへ通知 ~ LINE Notifyを使って防犯装置を作る | kunimiyasoft

わたしはこちらのサイトを参考にしました。

 

センサーの作り方

次に一番重要なセンサーについて解説します。

センサーはこのように雪の重みで傘がつぶれて反応するようにしました。

赤丸のところがセンサー

実際に作ってみたい方は、オリジナルのセンサーを作っても面白いかもですね。

 

全体のプログラム

 



#include <M5StickCPlus.h>
#include <ssl_client.h>
#include <WiFiClientSecure.h>
#include <Ticker.h>

// 使用するGPIOポート --------
#define WATER_PIN  36 // 雪センサー

// Wi-Fi設定 --------
const char *WIFI_SSID = "**********"; //Wi-FiSSID
const char *WIFI_PASS = "***********"; //Wi-Fiのパスワード

// LINEの設定 -------- トークン発行 さっき紹介したサイトを参考に
bool USE_LINE_NOTIFY = true;
const char* LINE_HOST = "notify-api.line.me";
const char* LINE_TOKEN = ""; //ゲットしたライントークンを入力

// 設定 --------
const int BATT_LOW_ALERM = 20;  // バッテリー残量アラーム(%) 低下時に通知


const int MUSIC_BPM = 120;

// マクロ定義 --------
#define sp(x) Serial.println(x)
#define spn(x) Serial.print(x)
#define spf(fmt, ...) Serial.printf(fmt, __VA_ARGS__)
#define lp(x) M5.Lcd.println(x)
#define lpn(x) M5.Lcd.print(x)
#define lpf(fmt, ...) M5.Lcd.printf(fmt, __VA_ARGS__)
#define array_length(x) (sizeof(x) / sizeof(x[0]))

// ================================================================================

// WiFiに接続する
void connect_wifi() {
  int i, stat;
  WiFi.mode(WIFI_STA);  // ステーションモード
  WiFi.disconnect();
  delay(100);
  WiFi.printDiag(Serial);
  while (true) {
    // 接続を試みる
    WiFi.begin(WIFI_SSID, WIFI_PASS);
    lpn("WiFi connecting...");
    spn("WiFi connecting...");
    for (i = 0; i < 150; i++) {
      stat = WiFi.status();
      spn(stat);
      if (stat == WL_DISCONNECTED) { //6
        spn(".");
        if (i == 149) {
          lpn("! ");
          sp("abort");
        }
      } else if (stat == WL_CONNECT_FAILED) { //2  (WL_NO_SSID_AVAIL=1)
        lpn("<<FAIL>>\n");
        sp(" err=" + String(stat) + " Failed");
        delay(3000);
        break;
      } else if (stat == WL_CONNECTED) { //3
        lp("<<OK>> connected!\n");
        lpn("IP= ");
        lp(WiFi.localIP());
        sp("\nWiFi connected!");
        spn("IP address: ");
        sp(WiFi.localIP());
        break;
      }
      delay(200);
    }
    // 接続できたか?
    if (stat == WL_CONNECTED) {  //接続成功 //3
      break;
    } else {  //接続失敗
      WiFi.disconnect();
      delay(3000);
    }
  }
}

// バッテリー残量の計算
int get_battery() {
  return round(((M5.Axp.GetBatVoltage() - 3.0f) / (4.2f - 3.0f)) * 100);
}

// ================================================================================ https POST

// LINE Notify メッセージ  絶対に変えるな!!
bool send_line(String message) {
  bool res = false;
  spn("connecting LINE server...");
  WiFiClientSecure sclient;
  sclient.setInsecure();  // 証明書の検証をしない
  if (sclient.connect(LINE_HOST, 443)) {
    String query = "message=" + message;
    String request = String("")
      + "POST /api/notify HTTP/1.1\r\n"
      + "Host: " + LINE_HOST + "\r\n"
      + "Authorization: Bearer " + LINE_TOKEN + "\r\n"
      + "Content-Length: " + String(query.length()) +  "\r\n"
      + "Content-Type: application/x-www-form-urlencoded\r\n\r\n"
      + query + "\r\n";
    spn("sending...");
    sclient.print(request);
    //sp(request);
    while (sclient.connected()) {
      String buff = sclient.readStringUntil('\n');
      //sp("|"+buff);
      break;
    }
    sp("done");
    res = true;
  } else {
    sp("failed");
  }
  sclient.stop();
  return res;
}

// ================================================================================ タイマー割込み

// BEEP音楽を再生する
Ticker ticker1;
int start_play_music = 0;
void play_music(int musicno) {
  start_play_music = musicno;
}

// バックグラウンドでBEEP音を再生する(10msごとに実行される関数)
void play_background() {
  static int musicno = 0;
  static int step = 0;
  static unsigned long stop_ms = 0;
  M5.Beep.update();

  // 再生開始
  if (start_play_music > 0) {
    musicno = start_play_music;
    start_play_music = 0;
    step = 0;
    stop_ms = 0;
  }

  // 次の音階を設定する
 
}

// ================================================================================ セットアップ

void setup() {
  M5.begin();
  M5.Beep.begin();
  Serial.begin(115200);

  // LED設定
  pinMode(M5_LED, OUTPUT);
  digitalWrite(M5_LED, HIGH); //OFF

  // 水位センサー ピン設定
  pinMode(WATER_PIN, INPUT);

  //ディスプレイ&フォント設定
  M5.Lcd.setRotation(3);  // 横向き・ホームボタンが左
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextColor(TFT_WHITE);
  M5.Lcd.setTextFont(1);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(0, 2);

  // BEEP設定
  M5.Beep.setBeep(2000, 100);
  M5.Beep.beep();

  // タイマー割込みの開始:
  ticker1.attach_ms(10, play_background);

  // WiFi接続
  connect_wifi();
  delay(1000);
  M5.Beep.beep();
  delay(200);
  M5.Beep.beep();
}

// ================================================================================ メインループ

void loop() {
  static int mode = 0;
  static int detect_cnt = 0;
  int i;
  bool res;
  M5.update();

  // 1秒おきにフラグを立てる
  static uint32_t tm = 0;
  static uint32_t old_ms = 0;
  bool refresh = false;
  if (tm == 0 || tm+1000 < millis()) {
    refresh = true;
    tm = millis();
  }
  if (millis() < old_ms) tm = 0;
  old_ms = millis();

  // メインボタンを1回押したらお湯はり開始
  if(mode == 0 && M5.BtnA.wasPressed()) {
    sp("Start");
    M5.Beep.beep();
    res = send_line("%0D%0A計測開始!");  // LINEに通知する
    if (!res) lp("Line Error!");
    mode = 1;
    refresh = true;
  } else if(mode >= 1 && M5.BtnA.wasPressed()) {
    // メインボタンをもう1回押したら終了
    sp("Shutdown...");
    M5.Beep.beep();
    mode = 3;
    refresh = true;
  }

  // 雪の状態をチェック
  int yuki = digitalRead(WATER_PIN);
  digitalWrite(M5_LED, yuki ? LOW : HIGH);
  if (mode == 1) {
    if (yuki) detect_cnt ++;
    if (detect_cnt == 1) {
        // ゆき
    }
    if (detect_cnt >= 500) {  // 5秒連続して検出したら (100=1s)
      mode = 2;
      refresh = true;
      sp("雪かき");
       
      send_line("%0D%0A雪かき!");  // LINEに通知する
    }
  }

  // モードに応じた画面表示
  static bool battnotified = false;
  if (refresh) {
    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.setCursor(0, 4);
    M5.Lcd.setTextSize(2);
    int battery = get_battery();  // バッテリー残量取得
    lp("Battery "+String(battery)+"%\n\n");
    switch (mode) {
      case 0:
        lp("Push to Start\n");
        break;
      case 1:
        lp("KEISOKU\n");
        M5.Lcd.setTextSize(1);
        lp("count="+String(detect_cnt)+"\n");
        break;
      case 2:
        lp("YUKIKAKI");
        break;
      case 3:
        lp("Shutdown...");
        break;
    }
    // バッテリー残量警告
    if (!battnotified && battery < BATT_LOW_ALERM) {
      battnotified = true;
      send_line("%0D%0Aモバイルバッテリーが接続されていません "+String(battery)+"%70");  // LINEに通知する
    }
  }

  // 電源オフ
  if (mode == 3) {
    WiFi.disconnect();
    delay(2000);
    M5.Axp.PowerOff();  // 電源オフ
  }

  // サイドボタンでモード戻す
  if(M5.BtnB.wasPressed()) {
    M5.Beep.beep();
    mode = 0;
    detect_cnt = 0;
    battnotified = false;
  }

  delay(10);
}

 

 

最後に

M5StickCplusとLineのつなげ方、全体のプログラムを紹介しましたが、

やはり屋外で使用するものなので防水機能が必須です。

降雪確認装置におすすめな防水グッズはこちらです。(実際に使用していたもの)

 

 

 

完成した装置を保温シートにクルクルっと包み込んで、目玉クリップで隙間をふさげば湖に沈めない限り、水没はしません。