猫トイレ接近通知とブラインドスポットモニターを作ろうと模索している。
っていうか猫トイレの接近検知システムは完成したのでブラインドスポットモニターはもう少し物理的設定が面倒だからここで見切って内容を公開する。
じゃないといつまで経ってもネタとして完了しないので。
猫トイレ接近通知システムと高級車のBSMは同じセンサーを使う?
HC-SR04
このセンサーは距離を計測できてcm単位でわかるすぐれた検知センサーだ。
周期的に計測してしきい値範囲内だったらアクションするというアルゴリズムを自分で組さえすればできてしまうのだ。
ちなみに高級車でも同じ原理だといったり同じセンサーを使うといっているが実際には高級車はもう少し良いものを使っているはずなので完全に鵜呑みにしないでほしい。
ただ自分は高級車に据え付けられているような高級なセンサーを自車につけるつもりはなく猫トイレにつけたものと同じHC-SR04で実現させるつもりだ。
ESP32またはESP8266
ESP8266は技適未対応なので使うときは自己責任にて。
Generic ESP32 DEVKIT V1 開発ボード 4M Flash デュアルコアCPU ESP-WROOM-32 NodeMCU (開発ボード)
¥798(2024/02/14 07:47時点の価格)
平均評価点:
>>楽天市場で探す
>>Yahoo!ショッピングで探す
【日本で全数チェック済】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時点の価格)
平均評価点:
>>楽天市場で探す
>>Yahoo!ショッピングで探す
ブレッドボート(なくてもできる)
サンハヤト SAD-101 ニューブレッドボード
¥319(2024/02/14 07:49時点の価格)
平均評価点:
>>楽天市場で探す
>>Yahoo!ショッピングで探す
ジャンパーワイヤー
オスーオス、オスーメス、メスーメスと種類があるので一通り揃えておくとよい。
VKLSVAN 3個 40本 10CM 多色40ピン デュポン ワイヤー ジャンパー ブレッドボード 接続ワイヤー (メス-メス) ArduinoとRaspberry piに適用(合計120本)
¥597(2024/02/14 07:50時点の価格)
平均評価点:
>>楽天市場で探す
>>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じゃなくてもいいけど。
【Works with Alexa認定】SwitchBot 防犯カメラ スイッチボット 監視カメラ ペットカメラ Alexa 屋内 カメラ ネットワークカメラ ベビーモニター スマートホーム 双方向音声会話 遠隔確認 取付簡単 防犯対策 小型 見守りカメラ セキュリティ(首振り)
¥4,585(2024/02/14 07:51時点の価格)
平均評価点:
>>楽天市場で探す
>>Yahoo!ショッピングで探す