[PR]記事内のアフィリエイトリンクから収入を得る場合があります

猫トイレ接近通知と高級車のブラインドスポットモニターは同じ原理だ

猫トイレ検知装置猫トイレ接近通知とブラインドスポットモニターを作ろうと模索している。

っていうか猫トイレの接近検知システムは完成したのでブラインドスポットモニターはもう少し物理的設定が面倒だからここで見切って内容を公開する。

じゃないといつまで経ってもネタとして完了しないので。

スポンサーリンク

猫トイレ接近通知システムと高級車のBSMは同じセンサーを使う?

HC-SR04

このセンサーは距離を計測できてcm単位でわかるすぐれた検知センサーだ。

周期的に計測してしきい値範囲内だったらアクションするというアルゴリズムを自分で組さえすればできてしまうのだ。

ちなみに高級車でも同じ原理だといったり同じセンサーを使うといっているが実際には高級車はもう少し良いものを使っているはずなので完全に鵜呑みにしないでほしい

ただ自分は高級車に据え付けられているような高級なセンサーを自車につけるつもりはなく猫トイレにつけたものと同じHC-SR04で実現させるつもりだ。

ESP32またはESP8266

ESP8266は技適未対応なので使うときは自己責任にて。

B0BZK2JJ94
Generic ESP32 DEVKIT V1 開発ボード 4M Flash デュアルコアCPU ESP-WROOM-32 NodeMCU (開発ボード)

¥798(2024/02/14 07:47時点の価格)
平均評価点:5つ星のうち3.5
>>楽天市場で探す
>>Yahoo!ショッピングで探す

B0964DC1DB
【日本で全数チェック済】MdskGang ESP8266 NodeMCU ESP-12E V2開発ボード WiFi Bluetoothワイヤレスモジュール、CP2102チップUSBシリアル変換アダプター、LUAスクリプトの使用、オープンソースシリアルモジュールはArduinoIDE/Micropythonにプログラミング最適、メイン周波数160MHz インターフェース AP、STA、AP + STAモードをサポート、ネットワークコントローラーを構築し スマートデバイスのネットワーク機能を追加し 機器の制御・監視用の最小システム

¥950 (¥6,333 / 100 g)(2024/02/14 07:48時点の価格)
平均評価点:5つ星のうち3.6
>>楽天市場で探す
>>Yahoo!ショッピングで探す

ブレッドボート(なくてもできる)

B00DSKCS68
サンハヤト SAD-101 ニューブレッドボード

¥319(2024/02/14 07:49時点の価格)
平均評価点:5つ星のうち4.4
>>楽天市場で探す
>>Yahoo!ショッピングで探す

ジャンパーワイヤー

オスーオス、オスーメス、メスーメスと種類があるので一通り揃えておくとよい。

B08BBXBFKD
VKLSVAN 3個 40本 10CM 多色40ピン デュポン ワイヤー ジャンパー ブレッドボード 接続ワイヤー (メス-メス) ArduinoとRaspberry piに適用(合計120本)

¥597(2024/02/14 07:50時点の価格)
平均評価点:5つ星のうち3.7
>>楽天市場で探す
>>Yahoo!ショッピングで探す

その他USB電源

ESPのマイコンボードによってMicroUSBのときとUSB-Cのときがあるから注意だけどこの記事を読むような人なら両方持ってるよな。

機能

LINE通知

LINE通知部分のプログラムは以下のようにやる。これはどこかのサイトでこのように教えてくれていたのでそのまま丸パクリである。引用元がわからなくなっているので無許可転載みたいになっているがもし自分のサイトだろって思ったらコメントください。

LINEからあてがわれたトークンというのはLINE notifyという機能を使う。登録するとTOKENが発行されるのでそれを以下のプログラムに当て込んで動かす。説明雑すぎ。

#include 
// LINE Notify設定
const char* LineHost = "notify-api.line.me";
const char* token = "LINEからあてがわれたトークン";
const char* message = "猫がトイレに入る";

// line通知
void send_line() {

  // HTTPSへアクセス(SSL通信)するためのライブラリ
  WiFiClientSecure client;

  // サーバー証明書の検証を行わずに接続する場合に必要
  client.setInsecure();
  
//  Serial.print("Try ");
  
  //LineのAPIサーバにSSL接続(ポート443:https)
  if (!client.connect(LineHost, 443)) {
    Serial.println("Connection failed");
    return;
  }
//  Serial.print("Connected  ");

  // リクエスト送信
  String query;
  query = String("message=") + String(message);
  String request = String("") +
               "POST /api/notify HTTP/1.1\r\n" +
               "Host: " + LineHost + "\r\n" +
               "Authorization: Bearer " + token + "\r\n" +
               "Content-Length: " + String(query.length()) +  "\r\n" + 
               "Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
                query + "\r\n";
  client.print(request);
 
  // 受信完了まで待機 
  while (client.connected()) {
    String line = client.readStringUntil('\n');
    if (line == "\r") {
      break;
    }
  }
  
  String line = client.readStringUntil('\n');
  Serial.println(line);
}

ここの件については特に理解をしているわけではなく定形プログラム通りに書いてコンパイルすれば自分のスマホにライン通知されるので気にしていない。

生存確認にBlynk使う(使わなくてもLINE通知はできる)

当然ながら自前サーバーで動かしているBlynkも活用しないとつまらない。ボードがちゃんと生きている証をBlynkサーバーに送り、それを手元のスマホで見て確認できるというわけだ。

つまり猫トイレ接近通知機能は猫がトイレに接近したときにはLINEで通知し、普段のボードがちゃんと稼働しているという通知はBlynkに対して行う。

生存報告部分のプログラムは以下のような感じ。

DisplayTime()というサブルーチンについてはこちらを参照してほしい。

BlynkのVPIN_TIMEというバーチャルピンのオフラベルには時刻を、VPIN_NAMEというバーチャル品のオンラベルにはIPアドレスの下3桁を書き込んでいる。

void aliveReport()
{
    char str[13]; //char str2[20];
    int int_h,int_m,int_s;
    DisplayTime(str,&int_h,&int_m,&int_s);         // 現在時刻取得
    Blynk.setProperty(VPIN_TIME, "offLabel",str);

    // 2023-04-20 IPアドレスを報告
    IPAddress ipaddr = WiFi.localIP();
    char ipadd[7];
    sprintf(ipadd, "IP:%d",ipaddr[3]);
    Blynk.setProperty(VPIN_NAME, "onLabel",ipadd);
}

BSM(距離センサー)フロー

初期設定

setup()では以下のような処理をやる。

void setup()
{
  pinMode(LEFT_LED_PIN, OUTPUT);//左LEDポートをOUTPUTに設定
  pinMode(Trig_Pin_L, OUTPUT);//トリガーピンをOUTPUTに設定
  pinMode(Echo_Pin_L, INPUT);//エコーピンをINPUTに設定
  digitalWrite(Trig_Pin_L, LOW);//トリガーピンに一発LOWを書き込んでおく
  delay(1);
  pinMode(RIGH_LED_PIN, OUTPUT);//以下右に関しても同様の処理を行う
  pinMode(Trig_Pin_R, OUTPUT);
  pinMode(Echo_Pin_R, INPUT);
  digitalWrite(Trig_Pin_R, LOW);
  delay(1);
  //dht.begin(); 温度センサー付けたらコメント外す
  //タイマールーチン定義(インターバルは100(ミリ秒))
  timer1.setInterval(TIMER_INTERVAL, onTimer); // check if Blynk server is connected every 3 seconds
  //左LEDと右LEDのコントロール処理を独立したスレッドとして定義する
  xTaskCreatePinnedToCore(left_led_control, "left_led_control", 4096, NULL, 2, &thp[0], 0);
  xTaskCreatePinnedToCore(right_led_control, "right_led_control", 4096, NULL, 3, &thp[1],0);
  //xTaskCreatePinnedToCore(
  //   [タスク名], "[タスク名]", 
  //   [スタックメモリサイズ(4096or8192)], [NULL], 
  //   [タスク優先順位(1-24)] 大きいほど優先順位が高い,
  //   [宣言したタスクハンドルのポインタ(&thp[0])], [CoreID(0or1)]); 
}

タイマーループ

//100ミリ秒ごとに呼ばれて主に各種カウンターをインクリメントする
void IRAM_ATTR onTimer(){
    portENTER_CRITICAL_ISR(&timerMux);
    //ここに変数変更を書き込む
    DistantCheckCounter ++;     // 距離計測タイマー
    AliveRepoCounter ++;        // 生存報告
    //TempCheckCounter ++;        // 温度計測センサー稼働させるときにはコメントはずす
    portEXIT_CRITICAL_ISR(&timerMux);  
}

ループ

ループ内での処理

void loop()
{
  timer1.run(); // Initiates SimpleTimer
  //ここからのif文はWi-Fi繋がっているかいないかで処理しているのでスタンドアロンで動かす場合には削除可能
  if (WiFi.status() == WL_CONNECTED) {
    // Wi-Fiに接続できた場合の処理
    ArduinoOTA.handle();          // OTAの命令を入れておく
    Blynk.run();
    // 5秒カウント判定
    if(AliveRepoCounter >= REPOINTERVAL / TIMER_INTERVAL){
      portENTER_CRITICAL(&timerMux);
      AliveRepoCounter = 0;
      portEXIT_CRITICAL(&timerMux);
      aliveReport(WL_CONNECTED);
    }
  } else {
    if (millis() - lastConnectionAttempt >= connectionDelay){
      lastConnectionAttempt = millis();
      // Wi-Fiに接続できなかった場合の処理
      Serial.printf("Wi-Fi try to connect %lu\n",millis());
      wifiMulti.run();
      Serial.printf("Wi-Fi end try        %lu\n",millis());
    }
  }
  //ここから左右の距離チェック
  if(DistantCheckCounter){
    portENTER_CRITICAL(&timerMux);
    DistantCheckCounter = 0;
    portEXIT_CRITICAL(&timerMux);
    //    0.1秒経過
    char str[10];
    distanceL = read_distance(TRIG_L);
    sprintf(str, "%.1lf", distanceL);
    Blynk.setProperty(VPIN_LEFT, "offLabel", str);
    distanceR = read_distance(TRIG_R);//戻り値はXXcm
    sprintf(str, "%.1lf", distanceR);
    Blynk.setProperty(VPIN_RIGH, "offLabel", str);
  }
//  EcoModeToggle();
}//loop

左距離計測してグローバル変数に格納

距離検知部分のプログラムは以下のように作った。

double read_distance(int LR(1または0)) {
  int temperature = 25;//外気温度によって検知する距離に微妙な差がでるので厳密に測定したかったらここの変数に温度センサーの値が入るようにすること
  double time = 0; //パルスが戻ってくる変数
  if(LR == TRIG_R){//TRIG_Rを1か0かあらかじめ定義しておく
    digitalWrite(Trig_Pin_R, LOW);//トリガーピンにLOWを書き込む
    delayMicroseconds(2);//ディレイする
    digitalWrite(Trig_Pin_R,HIGH);//トリガーピンにHIGHを書き込む
    delayMicroseconds(11);//ディレイする
    digitalWrite(Trig_Pin_R, LOW);//トリガーピンにLOWを書き込む
    time = pulseIn(Echo_Pin_R, HIGH);//パルスが戻ってくるまでの時間を変数に入れる
  }else{
    //左も勝手違いで同様の処理を入れる
  }
  // 音速  331.5 + (0.6 * 摂氏温度)
  double sonic = 331.5 + ((int)temperature * 0.6);//音速と摂氏温度の関係で距離計測はこういう式らしい

  double dist = (time * sonic * 100) / 1000000 / 2;//さらに距離(cm)はこの計算式で出す
  #if defined(DEBUG)
  Serial.printf("distance is %3.0lf cm\n",dist);
  #endif
  return dist;//モジュールの戻り値に距離(cm)をあてる
}//read_distance

右距離計測してグローバル変数に格納

右側も左同様で

別スレッド

setup()内で定義しておいた2つのスレッドが独立して動いて左右のLEDを灯したり消したり。

左LEDをONしたりOFFしたり

distanceLはグローバル変数でloop()内の処理で測定して数値(距離cm)がしきい値以内に入っていたらLEDをONするし、範囲外に出たならLED消灯する。

void left_led_control(void *args)
{
  int led_status = LOW;
  while(1){
    // 0または測定可能距離を超えていたら消灯
    if((distanceL == 0) || (distanceL >= Maximum_measurable_distance)){
      if(led_status == HIGH){
        led_status = LOW;
        // LEFT LED OFF
        digitalWrite(LEFT_LED_PIN, LOW);
      }
    }else{
      if(led_status == LOW){
        led_status = HIGH;
        // LEFT LED 点灯
        digitalWrite(LEFT_LED_PIN, HIGH);
      }
    }
    delay(1);
  }
}//left_led_control

ここで距離によって点滅させたりしても良いと思うがおそらくウザくなるからやらないほうがいいかも。

右LEDをONしたりOFFしたり

void right_led_control(void *args)というルーチンで勝手違いのロジックを作る。

猫トイレ検出装置のフロー

初期設定

猫トイレ検出装置のほうは左右のLEDとかコントロールしないでシングルの距離センサーを監視するだけなのでマルチスレッド対応のESP32ではなくESP8266を前提としたプログラムになっているのであしからず。

void setup() {
  Serial.begin(115200);
  Serial.println();
  // ファイル名 バージョンナンバー表示 ここらへんは本機能とは無関係なので消して可
  int lastIndex = programName.lastIndexOf('\\');
  String result = programName.substring(lastIndex + 1);
  Serial.printf("Program name : %s\n",result.c_str());
  Serial.printf("firmware version : %s\n",BLYNK_FIRMWARE_VERSION);
  WiFi.mode(WIFI_STA);
  // ポート初期化
  pinMode(LED_BUILTIN, OUTPUT);
  delay(10);
  // Connect to WiFi
  digitalWrite(LED_BUILTIN, HIGH);
  Serial.println("Wi-Fi Multi entry...");
  WiFi.disconnect(true);//Wi-Fiマルチエントリーの前にこれらを入れておくと
  delay(100);          //繋がりやすくなるらしい
  for (unsigned int i = 0; i < ROUTERS; i++) {
    wifiMulti.addAP(ssid_name[i], passwords[i]);   // add Wi-Fi networks you want to connect to
  }
  Serial.println("Connecting to Wi-Fi");
  while(WiFi.status() != WL_CONNECTED){
    wifiMulti.run();
    Serial.print(".");
    delay(500);
  }
  ArduinoOTAProcess();//Arduino OTAの処理は別プロセスにしてすっきりさせた
  Serial.println("ArduinoOTA Process done");
  BlynkConnectProccess();//Blynkの初期設定も別プロセスにしてすっきりさせた
  Serial.println("BLYNK connect Process done");
  //↓ここでトリガーピンとエコーピンの初期化してる
  pinMode(LEFT_LED_PIN, OUTPUT);
  pinMode(TriggerPin, OUTPUT);
  pinMode(Echo_Pin, INPUT);
  digitalWrite(TriggerPin, LOW);
  delay(1);
  // ここの下数行はタイマー割り込みルーチンを設定するお決まりの命令
  timer1.setInterval(TIMER_INTERVAL, onTimer); //
  ////////////////////////////////////////////////////////////////////////////  
  led_blink(50, 20, 20, LEFT_LED_PIN);  

  Serial.println("\nSetup done");
}//setup

ループ

距離計測

さっきのBSMの距離計測とどこかが違うと思うけど説明しきれない。解読できる人ならできると思う。たぶん猫だから入口にしばらく数秒うろつくからチャタリング防止みたいな処理を足してる。

// 磁石のチャタ防止スキャン処理
// 第2引数:現在のHIGH,LOWステータス
// 第3引数:GPIO変化ありでチャタ計測中フラグへのポインタ
// 第4引数:計測開始時のmillisへのポインタ
// 戻り値:GPIO状態(HIGH離脱 or LOW接続)
bool scanDistance(bool currentStatus,bool *startDebounce, unsigned long *lastDebounce)
{
  bool retValue = currentStatus;
  double distanceL = read_distance();
  // 距離報告時間ならアップロード
  if(distance_report > DISTANCE_REPORT_THRESHOLD / TIMER_INTERVAL){
    distance_report = 0;
    Blynk.virtualWrite(VPIN_DIST, distanceL);
    char dist[3];
    sprintf(dist, "%2.0lfcm", distanceL);
    Blynk.setProperty(VPIN_DIST,"offLabel", dist);
  }
  // 距離がしきい値を下回っていたら
  if(distanceL <= THRESHOLD_MAX_cm){ if(distanceL >= THRESHOLD_MIN_cm){
      if(!(*startDebounce)){
        *startDebounce = 1;
        *lastDebounce = millis();
      }else{
        if((millis() - *lastDebounce) > DEBOUNCETIME){
          retValue = HIGH;
          *startDebounce = 0;
        }
      }
    }else{
      retValue = LOW;
    }
  }else{
    *startDebounce = 0;
    retValue = LOW;
  }
  return retValue;
}

上位プログラム内でBlynk.setProperty(VPIN_DIST,”offLabel”, dist);というところがあるがこれでBlynkサーバーのボタンウィジェットに現在の距離を送信している。それが以下の画像だ。

猫トイレ検知システム

29cmというのが猫さまがトイレに入っていないときの下の踏み台かなにかまでの距離を表している。これが猫が近づくと10cm程度の距離に縮まるので通知を飛ばすというわけだ。

その下のボタン類は細かな設定もダイナミックにできるようにしてある。すなわち猫が入り口付近での滞在時間(秒)これは一瞬でも誤作動したときに通知が来たらうざいから3秒とかにしておくと3秒間以上距離の変動があったら通知するようにとかできる。

その他距離の範囲を設定できたりもするようにしてある。これらは一度設定すればボタンそのものを消してしまっても大丈夫かと思うがハードコーディングしていないと数値がデフォルトに戻ってしまうのでちょっと面倒。

しきい値内ならLINE通知ルーチン呼ぶ

void loop()
{
  timer1.run(); // Initiates SimpleTimer
  ArduinoOTA.handle();          // OTAの命令を入れておく
  if (WiFi.status() != WL_CONNECTED){
    // (optional) "offline" part of code

    // check delay:
    if (millis() - lastConnectionAttempt >= connectionDelay){
      lastConnectionAttempt = millis();
      // attempt to connect to Wifi network:
      Serial.printf("Wi-Fi try to connect %lu\n",millis());
      wifiMulti.run();
      Serial.printf("Wi-Fi end try        %lu\n",millis());
    }
  }else{
    // 5秒カウント判定
    if(AliveRepoCounter >= REPOINTERVAL / TIMER_INTERVAL){
      AliveRepoCounter = 0;
      aliveReport();//blynkサーバーに生存報告する
    }    
    if(CatCounter >= CATINTERVAL / TIMER_INTERVAL){
      CatCounter = 0;
      notice_status = LOW;      
    }
    if(DistantCheckCounter){ //0.5秒ごとスキャン
      DistantCheckCounter = 0;
      CurrentStatus = scanDistance(CurrentStatus, &StartDebounce, &LastDebounce);
      if(CurrentStatus){//2秒範囲内判断
        char str[13]; //char str2[20];
        int int_h,int_m,int_s;
        DisplayTime(str,&int_h,&int_m,&int_s);         // 現在時刻取得
        if(int_h >= notice_h_st && int_h <= notice_h_en){
          notice_cat_detect();
        }
      }
    }
    Blynk.run();
  }
}//loop

void notice_cat_detect()
{
  if(notice_status == LOW){
    notice_status = HIGH;
    send_line();//LINE通知
  }
}//notice_cat_detect

まとまらないけどまとめ

冒頭でも書いたが猫トイレ接近センサーは順調に動いてるので体重の近い猫様のうち誰がトイレに入ったのかリアルにわかるようになった。

というのは当然距離センサーは距離がわかるだけで体重まではわからない。トイレ前に仕込んだSwitchBotカメラですかさず見るようにして今誰がトイレに入っているかわかるというものだ。

一方、ブラインドスポットモニターはまだ物理的に自動車に取り付けていないので精度の確認が未達だ。いずれ取り付けたら動画でも撮ってまた公開したい。

結局この記事のコードはツギハギだらけなのでわからんから教えてほしいという方がいたらコメントください。

猫がトイレに入った瞬間が通知されればあとはトイレ前に仕込んであるSwitchBotカメラで確認してどの猫ちゃんがトイレに入ったんだなとわかるようになる。SwitchBotじゃなくてもいいけど。

B09KCFPMS3
【Works with Alexa認定】SwitchBot 防犯カメラ スイッチボット 監視カメラ ペットカメラ Alexa 屋内 カメラ ネットワークカメラ ベビーモニター スマートホーム 双方向音声会話 遠隔確認 取付簡単 防犯対策 小型 見守りカメラ セキュリティ(首振り)

¥4,585(2024/02/14 07:51時点の価格)
平均評価点:5つ星のうち4.0
>>楽天市場で探す
>>Yahoo!ショッピングで探す