Raspberry Piで、インターホンが鳴ったらプッシュ通知

家にいてインターホンが鳴っても気付かない時があったので、インターホンが鳴ったらスマホ宛にプッシュ通知を飛ばすようにしました。賃貸住宅なので勝手にインターホンをハックしたりはできないので、工事等が不要な方法を使います。

概ね下記記事と同様にRaspberry PiでGroveの光センサーを使ってインターホンの画面が付くのを検知し、SlackのIncoming Webhookを叩きました。

https://qiita.com/inui_yui/items/776c62c1774239081e7d

## 環境
### ハードウェア面
- [Raspberry Pi 4 Model B](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/) メモリ2GB
    - OSインストール済SDカード同梱の物を購入
- [GrovePi+](https://jp.seeedstudio.com/GrovePi.html)
- [Grove - Light Sensor](https://wiki.seeedstudio.com/Grove-Light_Sensor/)
- [Grove - LED](https://jp.seeedstudio.com/Grove-Blue-LED.html)
    - 本来不要ですが、Grove自体の動作確認等のためにあったほうが便利です
    - 特に私の場合「デジタル入出力のLEDは動くがアナログ入出力であるLight Sensorは動かない」という事象が起きたため、問題の切り分けのためにあってよかったと思います
- [Grove - Wrapper](https://wiki.seeedstudio.com/Grove-Wrapper/)
    - レゴにぴったりハマるセンサーのカバーです
- [レゴ クラシック 10696 黄色のアイデアボックス ＜プラス＞](https://www.lego.com/ja-jp/product/lego-medium-creative-brick-box-10696)
    - レゴも買いました

その他、Raspberry Piセットアップ時には有線LANケーブル、有線USBキーボード・マウス、microHDMIケーブルが必要です。一度セットアップした後はローカルのWi-FiネットワークでSSHすればこれらは不要になります。

### ソフトウェア面
- Raspberry Pi debian_version 10.8
- Python 3.7.3
- GrovePiファームウェアバージョン 1.4.0

## 準備
### Raspberry Piのセットアップ
本来Raspberry PiのセットアップはOSインストールメディアを焼く所から始めますが、今回OSインストール済SDカード同梱の物を購入したため省略します。Raspberry Piを起動するとOSのセットアップが始まりますがこちらも省略します。Linuxに慣れていれば特に問題は無いと思いますし、Web上にも様々な解説記事等が挙がっています。私は下記動画を見てセットアップの流れを掴んでおきました。

https://youtu.be/rf4AFCFs8Ao?t=382

OSが使えるようになったら、「設定」→「Raspberry Piの設定」→「インターフェイス」から、SSHとI2Cを有効にしておきます。Web上にある記事によっては`raspi-config`コマンドからAdvanced Optionsを変更する、といった手順が紹介されている事がありますが、どうやらRaspberry Piのバージョン等によって異なるようです。

Raspberry Pi自体の初期設定には色々な記事がWeb上にありますが、個人的にはSSH接続設定とetckeeperインストールは実行しておいた方が良いと思います。

```sh
$ sudo apt install etckeeper
```

また私の場合は、プロセスを動かしっぱなしにしたりSSHが切れても作業を続行するためにscreenコマンドを準備しておきました。

### GrovePi+と光センサーのセットアップ
RaspberryPiの準備ができたら、次にGrovePiのセットアップをしていきます。

1. RaspberryPiとGrovePiを接続
2. GrovePiのA0にlight sensorを接続
3. （LEDを使うなら）D4にLEDを接続

下記公式ガイドに沿って、インストールしていきます。

https://www.dexterindustries.com/GrovePi/get-started-with-the-grovepi/setting-software/

私はこの後の工程で相当ハマったので色々なコマンドを試しましたが、たぶん`$ curl -kL dexterindustries.com/update_grovepi | bash`と`$ sudo reboot`だけで良いと思います。

先程の公式ガイドにある`$ i2cdetect -y 1`というコマンドで`04`が下記のように表示されたら成功のようです。

```sh
$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- 04 -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --   
```

### Pythonで光センサーの値を取得
適当なディレクトリ（私の場合は`~/dev/grove-light-sensor`）に、Pythonのプログラムを記述していきます。

まず`~/Dexter/GrovePi/Software/Python/grovepi.py`にGrovePiを扱うライブラリがありますので、コピーして持ってきます。

```sh
$ cd ~/dev/grove-light-sensor
$ cp ~/Dexter/GrovePi/Software/Python/grovepi.py ./
```

次に、このライブラリを使って光センサーのプログラムを書いていきます。

```python:main.py
import grovepi
from time import sleep

while True:
    light = grovepi.analogRead(0)
    print(light)
    if light >= 100:
        print("light!")
    sleep(1)
```

あとは`$ python3 main.py`すれば動く...はずなのですが、実際には`grovepi.analogRead(0)`の所でプログラムが止まってしまいました。Web上の情報を調べてみるとファームウェアバージョンが古い場合にこのような事象が起きることが多いようなのですが、アップデートしても直りませんでした。どうも[この記事](https://qiita.com/yukataoka/items/9df2c74f7cd514e04b97#grove-pi%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%A3%E4%B8%8D%E5%85%B7%E5%90%88)によるとgrovepi.pyの不具合修正が必要のようでした。いろいろprintデバッグで調べた結果、私の場合は下記変更をしました。

```diff:grovepi.py
def read_identified_i2c_block(read_command_id, no_bytes):
        data = [-1]
-       while data[0] != read_command_id[0]:
+       while len(data) <= 1:
                data = read_i2c_block(no_bytes + 1)

        return data[1:]
```

この修正は下記Pull Requestにしましたが、正直に言って**私自身もこれは何を直しているのかあまり分かってない**ので自信は無いです。

https://github.com/DexterInd/GrovePi/pull/500

こうして、下記のような感じで適当な値が取れるようになったら成功です。

```sh
$ python3 main.py
10
10
10
```

この値が光によって上下します。私の場合、自宅の南向き窓の近くだと太陽光が常にあたって700ぐらいの値になりました。手でセンサーを覆うと0になり、インターホンに近づけて光を付けると50〜100ぐらいになりました。

### LED点滅の実装
インターホンの検知だけならば必要はありませんが、GrovePiのD4にGrove LEDを刺してから下記プログラムを実行するとLEDを光らせる事ができます。

```led.py
import time
import grovepi

led = 4

def blink(sec=60):
    for _i in range(sec):
        grovepi.digitalWrite(led, 1)
        time.sleep(0.5)
        
        grovepi.digitalWrite(led, 0)
        time.sleep(0.5)

if __name__ == "__main__":
    blink(10)
```

`$ python3 led.py`で、10秒間点滅すると思います。

先述の`analogRead()`が動かない場合などに、こちらのプログラムで`digitalWrite()`は動くかどうかの検証もできます。

### Slack通知の実装
PythonからSlackへ通知を送る方法は多くの情報があるため省略します。SlackでIncoming Webhookを設定して、POSTリクエストを送ればなんでもOKです。[こちらの記事](https://qiita.com/yhidetoshi/items/b717a95e0986a41784bc)の方法がライブラリ等も必要なく楽だと思います。

最終的に、光センサーを検知するメインプログラムは下記のようになりました。

```python
import grovepi
# slack.pyというファイルにdef post_slack():で通知関数を実装した場合
from slack import post_slack
# led.pyというファイルにdef blink():でLED点滅関数を実装した場合
from led import blink

from time import sleep
from datetime import datetime

light_before = float('inf')

while True:
    sleep(1)
    light_now = grovepi.analogRead(0)
    # たまに異常値が返ってくるため、その場合はスキップ
    if light_now == 65535:
        continue
    # 前回との比較で明るさが10以上上がっていたら
    if light_now - light_before >= 10:
        post_slack("インターホンがなったと思うよ 明るさ:{}".format(light_now))
        # 連続して通知が行かないよう待つついでにLEDを光らせる。LEDが無い場合はsleep(60)
        blink(60)
    print('{},{}'.format(datetime.now(), light_now))
    light_before = light_now
```

### プロセス監視
光センサー検知プログラムが動いているかどうかを監視し、止まっていた場合もSlackに通知するようにします。まずは下記のようなプログラムを記述します。

```python:process_check.py
import psutil
# slack.pyというファイルにdef post_slack()で通知関数を実装した場合
from slack import post_slack

ok = False

for ps in psutil.process_iter():
    if ps.cmdline() == ["python3", "main.py"]:
        print("ok!", ps.pid)
        ok = True
        break

if ok == False:
    post_slack("プロセスが動いてないかも")
```

これを`/etc/crontab`で適当なタイミングに定期実行させておきます。

```sh:/etc/crontab
# 1時間に1回動かす例
25 *    * * *   pi /usr/bin/python3 /home/pi/dev/grove-light-sensor/process_check.py
```

### 本番環境（自宅のインターホン）へのデプロイ
自宅のインターホンの近くには通常ディスプレイやキーボード・マウスを置きづらいと思われるので、それらを外してもRaspberry Piが動かせるようWi-FiからSSH接続できることを確認しておきます。電源だけはインターホンの近くに必要になります。

私の家のインターホンは画面があるので、その一部分を光で検知できるようにセンサーをGrove Wrapperとレゴブロックで覆いました。センサーが室内の照明等の影響を受けないように覆う必要がありますが、レゴブロックがぴったりでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/3bd18c52-37c3-53fc-a0c1-bb1e9b7e7e96.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/ddcbd583-b074-a369-259f-f506bb39dca7.png)

インターホン画面の近くの箱状の部分の中にLight Sensorがあります。下にあるのはRaspberry Pi本体のケースをレゴブロックでついでに作ったものです。私の自宅にはインターホンの下にカラーボックスがあるのですが、それとレゴの高さを足してもインターホンまで届かなかったので、一旦[その辺にあったデカい箱](https://www.playstation.com/ja-jp/ps5/)の上に置いています。

動物やTNTがあるのは、レゴショップに[マインクラフトのレゴブロック](https://www.lego.com/ja-jp/product/the-taiga-adventure-21162)があったのでついでに買ったものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/df1638af-3292-6506-6a95-ed09d4fd67e8.png)

これでインターホンを鳴らした所、無事にSlackから通知が届きました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/7a5bfebf-befd-4923-ce80-e48a49dca933.png)

## まとめ
Raspberry Piを使って、インターホンが鳴ったらプッシュ通知が飛ぶようにできました。

### 参考文献
- [ピンポンが鳴ったらLINE通知が来るようにインターホンをハック \- Qiita](https://qiita.com/inui_yui/items/776c62c1774239081e7d)
- [Raspberry PiでGrove Pi\+スターターキットとカメラを使う初期設定 \- Qiita](https://qiita.com/yukataoka/items/9df2c74f7cd514e04b97#grove-pi%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%A3%E4%B8%8D%E5%85%B7%E5%90%88)
- [はんだ付けできなくても大丈夫！ラズベリーパイとGrovePi\+で現実世界の情報を読み取ろう【センサー編】 \| 株式会社LIG](https://liginc.co.jp/309311)
- [GrovePi\+のフリーズ解消のもよう: イントロンのブログ](http://intron.cocolog-nifty.com/blog/2019/09/post-a05ce5.html)
- [Grove \- Light Sensor](https://seeeddoc.github.io/Grove-Light_Sensor/)