一定時間感知しなかったらお知らせを作ってみた

#はじめに
以前「感知したらお知らせ」を作っていましたが、今回「一定時間感知しなかったらお知らせ」を作ってみました。
以前「感知したらお知らせ」の記事はこちら。https://qiita.com/MikH/items/9389958fc1106a3a476c

#課題
離れたところに住んでいる一人暮らしの母が、急に体調が悪くなった際や、万が一のことが起きた際に、なかなか気づくことができない。カメラは設置したくない。生活動線に人感センサーを設置し、「感知したらお知らせ」は頻繁にお知らせが来すぎて、今回の目的では、実用的でない。一定時間（24時間とか12時間とか）感知しなかったら、知らせてほしい。

#課題解決
M5StickCとM5StickC PIR Hatで作った。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269534/4281706b-b600-c752-3223-4c7e5dd74977.png)

特徴としては

+ 生活動線(トイレなど必ず通る場所）に人感センサーを設置する。
+ 「一定時間感知しなかったら」のために、配列を使う。
+ どの時間帯で感知して、どの時間帯で感知しなかったのかを視覚化する。
　ディスプレイ下に並ぶ棒は24本あり、1時間の時間帯で感知した回数を棒グラフで表示している。一番左の棒は、24時間前から23時間前の1時間帯の感知回数を棒グラフ表示。一番右は直近の1時間帯の感知回数を表示。棒がない（下のドットのみ）時間帯は、感知していない時間帯で、外出していたり寝ていたり（または万が一のことが起きていたり）で生活活動が感知されていない状態。
+ 生活活動が感知されていない状態が24時間続くと（24本の棒グラフが全く表示されない（下のドットのみ）状態になると）、M5StickC内の赤色LEDが光って、お知らせする。お知らせはLINE notify, microsoft teams, IFTTTのどれかを選択する。
+ 何回お知らせしたかをディスプレイ1行目に表示する。
+ 正面Aボタンを押すと「24時間感知しなかったらお知らせ」を「12時間感知しなかったらお知らせ」に変更する。テストモードとして「30秒感知しなかったらお知らせ」モードも入れておく。
+ ディスプレイ右下の赤字Sは人感センサーが感知しているときに表示。
+ Ambientには1分おきに感知回数データを送って遠隔でも状態を監視できる。


以下のスケッチをM5StickCに書き込む

IFTTTは、IFTTTWebhookというライブラリを使っているので、IFTTTを使う場合はArduino IDE内に事前にインストールしておく必要あり。

```cpp
#include <M5StickC.h>
#include <WiFi.h>
#include <ssl_client.h>
#include <WiFiClientSecure.h> 
#include <HTTPClient.h> 
#include "IFTTTWebhook.h" //IFTTT用
#include "Ambient.h"

WiFiClient client;
const char* ssid = "xxxxxxxxxxxx";  // Wi-Fi_SSID
const char* password = "xxxxxxxxxxx";  // Wi-Fi_password
const char* token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";  //INPUT your LINE notify token ColorSensor3
const char* host = "notify-api.line.me";

Ambient ambient;
unsigned int channelId = xxxxx; // AmbientのチャネルID
const char* writeKey = "XXXXXXXXXXXXXXXX"; // Ambientのライトキー

//IFTTT
#define IFTTT_API_KEY "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"  //INPUT your API_KEY 
#define IFTTT_EVENT_NAME "xxxxxxxxxxxxxxxxx" //INPUT your event name

//IFTTT on IFTTTWebhook
IFTTTWebhook wh(IFTTT_API_KEY, IFTTT_EVENT_NAME);


#define LED_PIN  10 //赤色LEDは GPIO 10
#define LED_ON  LOW
#define LED_OFF HIGH

const int pir_sensing = 36;
int last_value = 0;
int cur_value = 0;

unsigned long previoustime = 0;
unsigned long previoustime2 = 0;

int total_count = 0;

int set_parameter = 0;
int icycle = 36000;
int alertcyclemin = 120;

int sensing_parameter = 1;

int alertsendtimes = 0;
int alert_set_parameter = 1;

int sequence[24] = {};
int SequencePosition = 0;

int ymax_set_parameter = 0;
float ymax =100;


void setup() {
  M5.begin();
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(BLACK);
  setup_wifi();
  pinMode(pir_sensing,INPUT_PULLUP);
  pinMode(LED_PIN,OUTPUT);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setCursor(15,15,2); M5.Lcd.print("Alarm for 24 hours");
  digitalWrite(LED_PIN, LED_ON);
  delay(100);
  digitalWrite(LED_PIN, LED_OFF);

   for (int i = 0; i < 25; i++) {
      M5.Lcd.drawPixel(15+i*5, 75, WHITE);
  }
  ambient.begin(channelId, writeKey, &client);
}


void loop() {
  int countHat = 0;
  auto now = millis();

  for (int i = 0; i < icycle; i++)  // default icycle=36000 => 3600sec=1h 
  {  
    if(M5.BtnA.isPressed()&& set_parameter == 0 ){
    set_parameter = 1;
    sensing_parameter = 1;
    //12hrs_High Sense
    icycle = 18000;
    alertcyclemin = 120;
    Serial.print("icycle: ");
    Serial.print(icycle);
    Serial.println(",");
    M5.Lcd.fillRect(15,15,145,13,BLACK); M5.Lcd.setCursor(15,15,2); M5.Lcd.print("Alarm for 12 hours");
    delay(100);
    }
    else if(M5.BtnA.isPressed()&& set_parameter == 1 ){
    set_parameter = 2;
    sensing_parameter = 1;
    //30sec__High Sense
    icycle = 12.5;
    alertcyclemin = 1;
    Serial.print("icycle: ");
    Serial.print(icycle);
    Serial.println(",");
    M5.Lcd.fillRect(15,15,145,13,BLACK); M5.Lcd.setCursor(15,15,2); M5.Lcd.print("Alarm for 30sec-test");
    delay(100);
    }
    else if(M5.BtnA.isPressed()&& set_parameter == 2 ){
    set_parameter = 0;
    sensing_parameter = 1;
    //1day_High Sense
    icycle = 36000;
    alertcyclemin = 120;
    Serial.print("icycle: ");
    Serial.print(icycle);
    Serial.println(",");
    M5.Lcd.fillRect(15,15,145,13,BLACK); M5.Lcd.setCursor(15,15,2); M5.Lcd.print("Alarm for 24 hours");
    delay(100);
    }

//PIRセンサーの0,1の値を読む
    cur_value = digitalRead(pir_sensing);// read the value of BUTTON

//PIRセンサーが感知したらcountHat値を1増加して、画面右下に感知しているマーク赤字S(Sensing)を表示
   if(cur_value == 1){
        countHat++;
        M5.Lcd.fillRect(145,65,15,20,BLACK);M5.Lcd.setCursor(145,65,2); M5.Lcd.setTextColor(RED); M5.Lcd.print("S"); M5.Lcd.setTextColor(WHITE);  
        digitalWrite(LED_PIN, LED_OFF);
        }else{
        M5.Lcd.fillRect(145,65,15,20,BLACK);  
    }

  //Current一番右の棒グラフ部分表示 count100で最大天井となり、その長さが40dotになるよう表示。 動的に徐々に棒グラフが増えていく
    if (countHat > 0){
    auto value = min(100, countHat);
    M5.Lcd.fillRect(135, 34, 3, 39, BLACK);
    M5.Lcd.fillRect(135, 75-(int)(value/ymax*40),3,(int)(value/ymax*40), WHITE);
    }
    else{
    M5.Lcd.fillRect(135, 34, 3, 39, BLACK);
    M5.Lcd.drawPixel(135, 75, WHITE);
    }
   
  delay(100);
  M5.update();

//Ambientに60秒ごとに現時点のcountHatを送信
  if (now - previoustime2 >= 60000){ 
  ambient.set(1, countHat);
  ambient.send();
  Serial.println("Just Sent to ambient");
  previoustime2 = now;
  }
  
}

//グラフプロット count100で最大天井となり、その長さが40dotになるよう表示。
  SequencePosition = (SequencePosition + 1) % (sizeof(sequence) / sizeof(int));
  sequence[SequencePosition] = countHat;
  int len = sizeof(sequence) / sizeof(int);
  M5.Lcd.fillRect(15, 30, 130, 45, BLACK);
  for (int i = 0; i < len; i++) {
      auto value = min(100, sequence[(SequencePosition + 1 + i) % len]);
      M5.Lcd.fillRect(15+i*5, 75-(int)(value/ymax*40),3,(int)(value/ymax*40), WHITE);
  }

//24の配列値を合計する
total_count = 0;
for (int i = 0; i < len ; i++) {
  total_count += sequence[(SequencePosition + 1 + i) % len ];
  }

//配列値の合計が0だったら本体の赤いLEDを点灯する。0以外で消灯する。
  if (total_count == 0){
      digitalWrite(LED_PIN,LED_ON);
  }
  else{
      digitalWrite(LED_PIN, LED_OFF);
  }

//全く感知しないとき（配列値の合計が0）、一定時間間隔でお知らせ  
  if (total_count == 0 && now - previoustime >= alertcyclemin *60 *1000){ 
      alertsendtimes++;
      send("動きがない"); // LINE
      Serial.println("LINEにメッセージを送った。");
      //send_message(); //Teams
      //Serial.println("Microsift Teamsにメッセージを送った。"); 
      //wh.trigger(); //IFTTT
      //Serial.println("IFTTTに送った。"); 
      
      Serial.print("alertsendtimes: ");
      Serial.println(alertsendtimes);

//何回送ったかをディスプレイ1行目に表示  
      M5.Lcd.fillRect(60,6,100,8,BLACK); M5.Lcd.setTextColor(RED);
      M5.Lcd.setCursor(60,6,1);  M5.Lcd.print("Sent");
      M5.Lcd.setCursor(90,6,1);  M5.Lcd.print(alertsendtimes);
      M5.Lcd.setCursor(105,6,1);  M5.Lcd.print("Alarm");      
      M5.Lcd.setTextColor(WHITE);      
      
      previoustime = now;
     
   }
}


 /* Wifiに接続する */
void setup_wifi() {
 
  M5.Lcd.fillRect(0,0,160,6,BLACK);
  M5.Lcd.setCursor(15,6,1);
  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.print("WiFi Connecting");
  Serial.println("WiFi Connecting");

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
   }
  Serial.println("");
  
  M5.Lcd.fillRect(15,6,145,8,BLACK);  M5.Lcd.setCursor(15,6,1);  M5.Lcd.println("WiFi");//
  M5.Lcd.setTextColor(WHITE);

  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}


 /* LINE Notifyに送る */
void send(String message) {
  WiFiClientSecure client;
  Serial.println("Try");
  //LineのAPIサーバに接続
  if (!client.connect(host, 443)) {
    Serial.println("Connection failed");
    return;
  }
  Serial.println("Connected");
  //リクエストを送信
  String query = String("message=") + message;
  String request = String("") +
               "POST /api/notify HTTP/1.1\r\n" +
               "Host: " + host + "\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');
    Serial.println(line);
    if (line == "\r") {
      break;
    }
  }
  String line = client.readStringUntil('\n');
  Serial.println(line);
}

 /* Microsoft Teamsに送る */
void send_message(){
  HTTPClient http;
  String url = "https://xxxxxxxx.webhook.office.com/webhookb2/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/IncomingWebhook/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";  //INPUT Teams Webhook URL
  if (http.begin(url))
  {
    String payload = "{'text':'Gauge Pin is connected.'}"; //INPUT your original message
    int httpCode = http.POST(payload);
  }
}
```

#まとめ
一定時間ロボットが止まっていたらお知らせとか、入院患者さんがトイレに行って一定時間たってもベッドに戻ってこなかったらお知らせとか応用例はありそうです。
取り急ぎ、
一人暮らしの親を静かに見守ることができて、ガッツポーズ！