PIRセンサを使えるようにする話

#1. はじめに

お仕事で 「人が居ない事を検知して下さい、カメラはダメよ！」とか言う
いつもの無茶振りで話は突然はじまった

むむむ？
人が居る事ぢゃなくって人が居ない事を検知？
なんだ？何に使うんだ？
って事で一旦なんとなく形になって来たのでというお話

#2. PIRセンサを真面目に考える
部品箱に入っている PIRセンサを取り出して

https://www.amazon.co.jp/gp/product/B07DCKZS5S

適当に繋いでみると実に不安定....に見える
これでは評価も低くなるなぁ

```test.ino

#define PIN (26)
void setup() {
  Serial.begin( 115200 );
  pinMode(PIN, INPUT_PULLUP);
}

void loop() {
  int val;
  val = digitalRead(PIN);
  Serial.printf("val=%d\n", val);
}
```

データシートを読んでみると何やら BLOCK_TIME と言うものがあり
この間は　反応をしないらしい
ははぁ　それで不安定に見えるのか...

https://datasheetspdf.com/pdf-file/775434/ETC/HC-SR501/1


#3. 改造？

WEBを検索しまくると

https://miraluna.hatenablog.com/entry/sensor_led

とか、ICの5PIN/6PIN直結の記事が目立つ
別にそれが良いとか悪いとかの話ではなくて
改造しちゃうと`量産`に耐えられなくなってしまうので
ここはソフトでカバーしまくることにした

* 量産の掟風なものがあり　買ってきたものは`改造`してはいけない
* っていうかだいたい内製してねとのお話

#4. 仕組み

下記のように BLOCK TIME と呼ばれる部分は 仮想的にONを出力しセンサからの値を出力しないようにする

```
//
//         +---+............
//         |   | BLOCK     :
//         |   |     TIME  :
// --------+   +----------------------------
// Status ON   OFF
//
//
//         +---+............
//         |   | VirtualON :
//         |   |      TIME :
// --------+   +----------------------------
// Status ON               OFF
//
//

```

さらに ON/OFF の状態が変わった時点から n分間の傾向を算出してみる
これが 0であれば過去n分間 そこに人が居なかった事がわかる

``` 
     +-+     +-+                  +-+
     | |     | |                  | |
     | |     | |                  | |
-----+ +-----+ +------....--------+ +----
     1回目   2回目                 n回目
<-------------- 過去n分間 --------------->
```


#5. 全ソース
``` PIR.ino
#include "myPIR.h"
const uint16_t LEDPIN = (10);
const uint16_t PIRPIN = (26);
const uint32_t ONE_SEC_MSEC = (1 * 1000);
const uint32_t FIVE_SEC_MSEC = (5 * 1000);
const uint32_t ONE_MIN_MSEC = (60 * ONE_SEC_MSEC);

//========================
// 初期化
//========================
void setup(void)
{
  Serial.begin( 115200 );
  pinMode(LEDPIN, OUTPUT);
  digitalWrite(LEDPIN, !LOW);
  setupPIR(PIRPIN, FIVE_SEC_MSEC);
}

//========================
// LOOP
//========================
void loop(void)
{
  int16_t val, cnt;
  val = getPIR();
  cnt = getAnyMinTrand(ONE_MIN_MSEC);
  digitalWrite(LEDPIN, !val);
  Serial.printf("val=%d cnt=%d\n", val, cnt);
  delay(1);
}
``` 
``` myPIR.h
//===============================================================> myPIR.h
//  HC-SR501
//  Author/date   : @chrmLinux 2021/09/29
//  Author/update : @chrmLinux 2021/09/29
//  https://datasheetspdf.com/pdf-file/775434/ETC/HC-SR501/1
//
//  +--------------------------+
//  |          . . .           |
//  |          G O V           |
//  |          N U C           |
//  |          D T C           |
//  || Repeat                  |
//  |. Single ==   ==          |
//  +--------------------------+
//    Sensitivity  TimeDelay
//             VR         VR
//    min <-> max  min <-> max
//
// 1)１分くらい待たないと安定しない
// 2)OFF直後に BLOCK TIME がある
//
//         +---+............
//         |   | BLOCK     :
//         |   |     TIME  :
// --------+   +----------------------------
// Status ON   OFF
//
//
// 3)ぢゃ、仮想的にONするって感じで
//
//         +---+............
//         |   | VirtualON :
//         |   |      TIME :
// --------+   +----------------------------
// Status ON               OFF
//
//

const uint32_t BLOCK_TM = (5 * 1000);
static uint16_t _pirPin = 0;
static uint32_t _blockTime = 0L;

//========================
// BLOCKING TIME秒数 待つ
//========================
bool waitBlockTime()
{
  static uint32_t tm = millis();
  bool rtn = false;
  if ((millis() - tm) > _blockTime) {
    tm = millis();
    rtn = true;
//    Serial.printf("[wait]\n");
  }
  return rtn;
}

//========================
// PIR初期化
//========================
void setupPIR(uint16_t pin, uint32_t blocktime)
{
  _pirPin = pin;
  _blockTime = (blocktime < BLOCK_TM) ? BLOCK_TM : blocktime;
  pinMode(_pirPin, INPUT_PULLUP);
}

//========================
// 設定したBlockTImeを得る
//========================
uint32_t getBlockTime(void)
{
  return _blockTime;
}

//========================
// PIRから実際に値を得る
//========================
int16_t getRealPIR(void)
{
  int16_t val = digitalRead(_pirPin);
  return val;
}

//========================
// PIRから仮想の値を得る
//========================
int16_t getPIR(void)
{
  static int16_t preVal = -1;

  if (preVal == HIGH) {
    if (!waitBlockTime()) {
      return HIGH;
    }
  }

  int16_t val = getRealPIR();
  if (preVal != val) {
//    Serial.printf("[getPIR] preVal:%d val:%d\n", preVal, val);
    preVal = val;
  }
  return val;
}

//========================
// センサ検知からn分間の傾向
//========================
uint32_t getAnyMinTrand(uint32_t any_sec_msec)
{
  static uint32_t tm = millis();
  static uint32_t cnt = 0L;
  static int16_t preVal = 0;
  int16_t val;

  if ((millis() - tm) > any_sec_msec) {
    tm = millis();
    cnt = 0L;
  }

  val = getPIR();
  if (preVal != val) {
    tm = millis();
    preVal = val;
    if (val == HIGH) {
      cnt++;
    }
  }

  return cnt;
}
//<=============================================================== myPIR.h

```
#6. おわりに

何かと癖がある PIRセンサですが
うまい具合に使うと色々な可能性が見えてきました
さて、クライアントはこれを何に使うのでしょうか...
私には見当もつきません(コロナ対策かな?)

* この処理が皆さんに広がって中途半端な故障の多い`PIRセンサ`とか言われているこれが見直される事を祈ります