2025年8月24日日曜日

LINEに通知する見守りセンサ(その2;プロトタイプ)

久しぶりの電子工作は、テレビドラマにもなっている ひとりでしにたい を読んでから孤独死が気になりだして作った、24時間ひとの動きが無かったときにLINEに通知する見守りセンサです。24時間だと心配なら12時間でもよいけど、ぐっすり寝ていたり冬の寒い時期は12時間くらい動き出さないことがあるかも。まぁこの辺は活動時間などをみて適宜調整。

LINEにメッセージを送る方法は以下のサイトを参考にさせて頂きました。利用するには事前にLINEへ登録する必要があります。

ひとの動きは人感センサで検出します。家の中で必ず動く場所、例えば冷蔵庫前やトイレなどの動線近くにセンサを置けば、1日に数回は検出されるはず。

電源を入れると最初に以下のようなメッセージがLINEに届きます。

以下はテストのために、動きが検出できないまま 1H経過で黄色LED点灯、2H経過で赤色LED点灯とLINEメッセージ送信に設定しています。本番では12H、24Hにします。

以下は1H経過後に黄色LEDが点灯している様子を少し離れたところからzoomで撮影。近づくと人感センサが反応して緑色に変わってしまうため。黄色表示は見ることないので、なくてもよいかも。


そして、人の動きが無くなって2H(本番では24H)経過後にメッセージが届いた様子です。LINEのタイムスタンプが3時間半近くになっていますが、途中でセンサに近づいたためです。何事もなければ24H以内にセンサに検知されるはずなのでメッセージは飛んできません。


届いたら誤報であることを願って電話などで安否を確認します。近所なら駆け付けられますが、遠方だと電話か近所の人に様子を見に行ってもらうとか。田舎だと近所付き合いも多少はあるので。

25.08.25追記
--
昨夜一晩動かしっぱなしにした結果です。2時間おき(本番は24時間おき)にLINEへメッセージが届いていました。ちゃんと動作している模様。朝、センサに近づいたら点灯していた赤色LED(動きが無くメッセージを送信した状態)が、緑色LEDの見守り状態に変わるのが確認できました。

--

回路図です。ESP32開発キットを使っているので、電源などはUSBから供給です。ユニバーサル基板に載せ替えるときにちゃんとした回路図を書くつもりです。


スケッチ(プログラム)です。無保証です。まだ24Hの動作確認はしていませんし、デバッグも不十分かと思っています。

処理内容は上記手書きのメモに準じています。不在スイッチ(旅行などのときに押す)を押してから、しばらくは検知しないようにしています。検知すると不在から復帰してしまうので。例えば家を出る20分前には押してもらうとかする。押し忘れても24Hおきにメッセージが来るだけなので、そんなに問題はないか(一度電話すれば確認できるので)。

テスト用に黄色まで1H、赤色(メッセージ送信)まで2H、不在スイッチ押下の保留時間1分にしてあるので適宜変更してください。

WiFiのSSID,PW、LINE Messaging APIのチャンネルID, PWは各自のものに変更してください。

//-----------------------------------------------------------------------------
//                                                              2025.08.24 naka
// LINE通知する見守りセンサ
// ・人感センサで活動をモニタリング
// ・起動時に見守り開始のメッセージをLINEに送る
// ・一定時間(24H)活動がないとメッセージをLINEに送る
// ・その後も活動がないと一定時間(24H)毎にメッセージを送り続ける
// ・旅行などで不在になる場合は「不在」スイッチを押すと見守り停止
// ・不在から復帰(人感センサが反応)したら、自動的に見守り再開
// ・LED表示で見守り状態を示す
//   - 緑LED点灯:見守り中、緑LED点滅:不在(見守り停止中)
//   - 黄LED点灯:12H活動反応なし
//   - 赤LED点灯:24H活動反応なし、LINEメッセージ送信
//-----------------------------------------------------------------------------
#define SENSOR      4
#define SW          5
#define LED_GREEN  15
#define LED_YELLOW 16
#define LED_RED    17

#include <WiFi.h>
#include <HTTPClient.h> 
// LINE messaging API
const String token_url = "https://api.line.me/oauth2/v3/token";
const String msg_url   = "https://api.line.me/v2/bot/message/broadcast";
const char ssid[]      = "XXXXXXXX";
const char password[]  = "XXXXXXXXXXXX";
const char channel_id[] = "XXXXXXXXXX";
const char channel_pw[] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
const String messageBegin    = "見守りを開始します。";
const String messageNoactive = "お母さんの活動が24時間不明です。至急連絡してください。";

// Timer setting
volatile uint32_t count = 0;
volatile uint32_t waitingTimeCount = 0;
volatile int      status = 0; // 0:見守り中, 1:12時間経過、2:24時間活動なし(メッセージ送信)、3:不在中 
//const uint32_t LimitRed    = 60 * 60 * 24;  // 24時間
//const uint32_t LimitYellow = 60 * 60 * 12;  // 12時間
//const uint32_t WaitingTime = 60 * 30;       // 30分 不在スイッチを押してからの退出までの待ち時間(不在スイッチを押した直後に人感センサが反応しないように)
const uint32_t LimitRed    = 60 * 60 * 2;  // 2時間
const uint32_t LimitYellow = 60 * 60 * 1;  // 1時間
const uint32_t WaitingTime = 60;           // 1分 不在スイッチを押してからの退出までの待ち時間(不在スイッチを押した直後に人感センサが反応しないように)

hw_timer_t * timer = NULL;
void IRAM_ATTR countUp() {
  if (status<3) count++;  // 見守り中, 3:不在中
  else if(status==3 && waitingTimeCount>0) waitingTimeCount--; // 不在スイッチを押してからの退出までの待ち時間カウントダウン
}

void setup() {
  pinMode(SENSOR,     INPUT);
  pinMode(SW,         INPUT);
  pinMode(LED_GREEN,  OUTPUT);
  pinMode(LED_YELLOW, OUTPUT);
  pinMode(LED_RED,    OUTPUT);
  digitalWrite(LED_GREEN,  LOW);
  digitalWrite(LED_YELLOW, LOW);
  digitalWrite(LED_RED,    LOW);
  status = 0;

  // タイマ割り込み設定
  timer = timerBegin(1000000);
  // Attach onTimer function to our timer.
  timerAttachInterrupt(timer, &countUp);

  // Set alarm to call onTimer function every second (value in microseconds).
  // Repeat the alarm (third parameter) with unlimited count = 0 (fourth parameter).
  timerAlarm(timer, 1000000, true, 0);

  // 動作確認のために動作開始メッセージを送る
  wifi_connect();              // WiFi接続
  send_msg2line(messageBegin); // LINE notifyに見守り開始メッセージを送る
  wifi_disconnect();           // WiFi接続解除

}

void loop() {
  if (waitingTimeCount>0) {}
  else if (digitalRead(SENSOR)==HIGH) {
    count  = 0;
    status = 0;
  }
  else if (count>LimitYellow && status==0) {
    status = 1;
  }
  else if (count>LimitRed) {
    count  = 0;
    status = 2;
    wifi_connect();                 // WiFi接続
    send_msg2line(messageNoactive); // LINE notifyに活動がない旨のメッセージを送る
    wifi_disconnect();              // WiFi接続解除
  }

  if (status==0) {
    digitalWrite(LED_GREEN, HIGH);    
    digitalWrite(LED_YELLOW,LOW);    
    digitalWrite(LED_RED,   LOW);    
  }
  else if (status==1) {
    digitalWrite(LED_GREEN, LOW);    
    digitalWrite(LED_YELLOW,HIGH); 
    digitalWrite(LED_RED,   LOW);
  }    
  else if (status==2) {
    digitalWrite(LED_GREEN, LOW);    
    digitalWrite(LED_YELLOW,LOW); 
    digitalWrite(LED_RED,   HIGH);
  }
  else if (status==3) {
    blinkLED(LED_GREEN);
  }

  // 不在スイッチ確認
  if (digitalRead(SW)==LOW) {
    while(digitalRead(SW)==LOW) delay(10); // チャタリング対策
    count  = 0;
    status = 3;
    waitingTimeCount = WaitingTime; // 待ち時間設定
  }

}

void blinkLED(int LED) {
  digitalWrite(LED,HIGH);
  delay(50);
  digitalWrite(LED,LOW);
  delay(900);  
}

void wifi_connect() {
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
    }

    // 以下5行は、普通は不要なハズ(我が家のアクセスポイントは無いと繋がらない)
    IPAddress gateway(192,168,0,1);
    IPAddress subnet(255,255,255,0);
    IPAddress dns1(8, 8, 8, 8); // Google DNS primery server
    IPAddress dns2(8, 8, 4, 4); // Google DNS secondary server
    WiFi.config(WiFi.localIP(),gateway,subnet,dns1,dns2);    
}

void wifi_disconnect() {
    WiFi.disconnect(true);
}

String get_token() {
    HTTPClient http;

    http.begin(token_url);
    http.addHeader("Content-Type","application/x-www-form-urlencoded");

    String  body = "grant_type=client_credentials&";
            body += "client_id=" + String(channel_id) + "&";
            body += "client_secret=" + String(channel_pw);
    int httpCode = http.POST(body);
    String token="";
    if(httpCode == 200){
        String S = http.getString();
        int i = S.indexOf("\"access_token\"");
        if((i>0) && (S.substring(i+15, i+16).equals("\""))){
            token = S.substring(i+16, i+16+174);
        }
    }
    else{ 
      // エラー:黄色LED 5点滅
      blinkLED(LED_YELLOW);
      blinkLED(LED_YELLOW);
      blinkLED(LED_YELLOW);
      blinkLED(LED_YELLOW);
      blinkLED(LED_YELLOW);
    }
    http.end();
    return token;
}

void send_msg2line(String message) {  
    String token = get_token();
    if (token.length()!=174) { // エラー:赤色LED 5回点滅
      blinkLED(LED_RED);
      blinkLED(LED_RED);
      blinkLED(LED_RED);
      blinkLED(LED_RED);
      blinkLED(LED_RED);
      return;
    }
  
    HTTPClient http;
    http.begin(msg_url);
    http.addHeader("Content-Type","application/json");
    http.addHeader("Authorization","Bearer " + token);
    String json = "{\"messages\":[{\"type\":\"text\",\"text\":\"" + message + "\"}]}";
    http.POST(json);
    http.end();

}

//---------------------------------------------------------------
// EOF
//---------------------------------------------------------------

0 件のコメント:

コメントを投稿