お手軽防犯システム「Ban-Ken Johnny」を作ってみた🐺

[MAN WITH A MISSION](https://www.mwamjapan.info/)
頭はオオカミ、体は人間の究極の生命体。
彼らは自分たちの音楽で世界征服を目指す、最高にアツい集団だ。

そんな「MAN WITH A MISSION」のGt,Vo,Rap ジャン・ケン・ジョニー(Jean-Ken Johnny)と番犬をかけあわせた、というのが「Ban-Ken Johnny（番犬ジョニー）」の由来である。
オオカミと番犬、イヌ科の奇跡のコラボレーション!
センスしかないですね(笑)
ファンクラブ会員限定の１０周年記念ツアー当たって欲しいなぁ...

はい、前置きはこれぐらいにして「Ban-Ken Johnny」の機能を紹介します。

### 「Ban-Ken Johnny」の機能
* 人感センサーで人の気配を感知したら、スマホにアラートメッセージを送る
* 自分が家にいる間はセンサーが反応して欲しくないので、監視機能の`ON/OFF`を切り替えできる。
* 毎回家出る時にon/offするのはメンドイので、家の外からオンラインでアラート通知を`ON/OFF`できる

「Ban-Ken Johnny」にできることはざっとこれぐらいです。
イメージがつきやすいように「Ban-ken Johnny」のデモをしました。
#### 1.監視機能をON
<img width="375" alt="fullsizeoutput_e4.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212819/88419883-463d-4255-443b-7ef55cc48368.jpeg">
<br>
監視機能はOFFになっているので、`start`ボタンを押してONにします。
<img width="375" alt="fullsizeoutput_e7.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212819/826fb713-c718-169c-e0f9-b6f70dbfab12.jpeg">
ONになりました。

#### 2.家に誰か侵入（ラズパイの人感センサーに反応）
監視機能がONの状態で、
![5DWEZ6kzT%+aQLpI5QcDxQ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212819/5b9367f5-7fe3-6435-c478-b66dc358f0cf.jpeg)
家に設置されているラズペリーパイの前を誰かが横切ります（なぜか物悲しい写真ですね...笑）

そうすると、
<img width="375" alt="fullsizeoutput_df.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212819/03e7e817-127d-c55b-33a0-7405a95f0a87.jpeg">
自分のスマホに通知が飛んできました！
まあこんな感じです笑
デモでイメージを掴んでいただいたところで、「Ban-Ken Johnny」の仕組みを紹介します。

### 「Ban-Ken Johnny」の概要
![Ban-Ken Johnny.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212819/4e133f1a-a3d7-acd3-a66b-96d7403cad14.jpeg)

図だけ見てもよくわからないと思うので、ここからは仕組みを少しだけ詳しく説明して行こうと思います。
興味ないよーという方は`編集後記`まで読み飛ばしてください

### 1.Mattermost
[Mattermost](https://docs.mattermost.com/)は自分が侵入者検知メッセージを受け取るために使用します。
別にSlackでも良かったのですが、勉強のためにも自分でチャット環境を用意してみることにしました。
今回MattermostはEC2上で`docker`を使って動かします。
個人利用なので、preview版を使うことにしました。
[mattermost/mattermost-preview](https://hub.docker.com/r/mattermost/mattermost-preview)
preview版は一つのコンテナ内に、mattermostアプリとmysqlが動作していて、コンテナ一つを動かすだけでmattermostを使用できます。

加えてmattermostの前段にnginxも置いてみることにしました。(特に理由はないです笑)
Mattermostは親切なので、Mattermost用の[nginx.conf](https://docs.mattermost.com/install/config-proxy-nginx.html)を用意してくれています。
これをありがたく使わせてもらいます。

それらを踏まえると、mattermostを動かすためのdocker-compose.ymlはこんな感じになります。

```yaml:docker-compose.yml
version: "3"
services: 
  nginx:
    image: nginx:1.17
    container_name: nginx
    volumes: 
      - "./etc/nginx:/etc/nginx"
      - "./var/log/nginx:/var/log/nginx"
    ports: 
     - 8080:80
    links: 
     - mattermost

  mattermost:
    image: mattermost/mattermost-preview
    container_name: mattermost
    volumes: 
      - "./mysql:/var/lib/mysql"
```

ディレクトリ構成はこんな感じです。

```
./mattermost
├── ./docker-compose.yml
├── ./etc
│   └── ./etc/nginx
│       └── ./etc/nginx/nginx.conf
├── ./mysql
└── ./var
    └── ./var/log
        └── ./var/log/nginx
```
今回はEC2で動かして、その前段にELBとRoute53を置きます（ELB置くならnginxいるのか？というツッコミはなしで）
Route53で設定したドメイン名（`ban-ken-johnny.example.com`とします）にアクセスしてみます
![mattermost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212819/99465026-52e3-c019-185c-6b211c440d8b.png)

はい、無事表示されました。
（よくわからないところで凝り性を発揮してしまい、https対応のために`AWS Certificate Manager`使って証明書発行したり、ALBで`HTTPtoHTTPS`対応したりしたんですが、長くなるので割愛します）

とりあえずアカウントを作成してログインします。
ログインしてやったこととしては、「Ban-Ken Johnny」がアラートメッセージを送って来れるように、[Incominng Webhook](https://docs.mattermost.com/developer/webhooks-incoming.html)の設定と、[プッシュ通知サーバーの設定](https://developers.mattermost.com/contribute/mobile/push-notifications/service/)をしました。
ただ自前でpush通知用サーバーを用意するほど余裕がなかったので、Mattermostが用意してくれているテスト確認用のサーバーを使うことにしました。
あんまり通知飛ばさないと思うのでMattermostさんご容赦を...

さて、もろもろを終えると「Ban-Ken Johnny」からWebhook経由でメッセージが送られてスマホに通知がくる、ということができるようになります。
<img width="375" alt="6C8105F5-A908-49DA-906A-656923A55571.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212819/8f8c8240-868b-2f19-39d4-fb4ad669f276.png">

<img width="375" alt="A4C0AB61-DA9A-4680-A77D-88C42AFF4F06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212819/5f1107b3-b5f6-e53d-97a4-b8f4fceda35f.png">
イイ感ジデスネ、YES!YES!


### 2. Flask App
オンラインからをアラート通知を`on/off`にするために、アラートの状態を管理してくれるアプリをFlaskで実装しました。
本当に簡単なアプリで、機能としては

* `/start`や`/stop`のGETリクエストがきたら、DBのstatusカラムの値を変更する

という本当にただそれだけのアプリです。
このアプリも`docker`を使ってEC2上で動かします。

```yaml:docker-compose.yml
version: "3"
services: 
  nginx:
    image: nginx:1.17
    container_name: nginx_flask
    volumes: 
      - "./etc/nginx:/etc/nginx"
    ports: 
     - 8081:80
    links: 
     - flask-app

  flask-app:
    build: ../
    image: flask-app
    container_name: flask-app
    environment:
      - DB_USER=root
      - DB_PASSWORD=setting_password
      - DB_HOST=mysql_flask
      - DB_NAME=flaskdb
    links: 
      - mysql

  mysql:
    container_name: mysql_flask
    image: mysql:5.7
    environment:
      - MYSQL_ROOT_PASSWORD=setting_password
      - MYSQL_DATABASE=flaskdb
    volumes:
      - ./sql:/docker-entrypoint-initdb.d
    command: "mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci"
```
ディレクトリ構成はこんな感じです。

```
./flask-app
├── ./Dockerfile
├── ./config
│   └── ./config/config.py
├── ./definitions
│   ├── ./definitions/__init__.py
│   └── ./definitions/database.py
├── ./docker
│   ├── ./docker/docker-compose.yml
│   ├── ./docker/etc
│   │   └── ./docker/etc/nginx
│   │       └── ./docker/etc/nginx/nginx.conf
│   └── ./docker/sql
│       └── ./docker/sql/init_ddl.sql
├── ./models
│   ├── ./models/__init__.py
│   ├── ./models/alert_status.py
│   └── ./models/dao
│       ├── ./models/dao/__init__.py
│       └── ./models/dao/alert_status.py
├── ./requirements.txt
└── ./server.py
```
これにより、アラートの状態を管理してくれる簡単なAPIが完成しました。

これぐらいなら`Amazon API Gateway → AWS lambda → Amazon DynamoDB`の完全サーバレス構成にできそうなので、時間ができたらサーバレス構成に変更して見たいと思います。

### 3. S3(Front End)
スマホからオンラインでアラートの`ON/OFF`を命令するためのフロントエンドは、[Amazon S3 での静的ウェブサイトのホスティング](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/WebsiteHosting.html)を使って動かすことにしました。
正直フロント技術はほとんど触ってきていないところで、かつデザインのセンスもないのでパパッと手軽に実装して、S3にデプロイしちゃいました。
本当はVue.jsとか使いたいし、CSSも初心者感を無くしたいし...
そんなのは夢のまた夢で、イケてない見た目になっちゃいました...
ごめんねジョニー...

### 4. RaspberryPi
さあ最後に、ラズパイを触っていきます。
「Ban-Ken Johnny」を作るために、今回初めてラズパイを購入しました。
今年一年頑張った自分へのクリスマスプレゼントですね

ラズパイには、「人感センサーをつけて、人の気配を検知したらMattermostにアラート通知する」という、根幹の部分を担ってもらいます。
このプロセスは常時動きながら家を監視しておいて欲しいので、`systemd`を使ってデーモンプロセスとして動かすことにしました。

プロセスとして動かすpythonのスクリプトはこんな感じ。

```python:ban-ken-johnny.py
#!/usr/bin/python3

import requests
import json
from time import sleep
from datetime import datetime
import dateutil.tz as tz
import sys
import RPi.GPIO as GPIO

GPIO_PIN = 18

GPIO.setmode(GPIO.BCM)
GPIO.setup(GPIO_PIN, GPIO.IN)

STOPPED_STATUS = 2
WEBHOOK_URL = 'https://ban-ken-johnny.example.com/hooks/abcdefghijklmnopqrstuvwxyz'

def is_alert_stopped():
    response = requests.get("https://ban-ken-johnny.example.com:8080/getAlertStatus")
    content = json.loads(str.strip(response.content.decode("utf-8")))

    if int(content['status']) == STOPPED_STATUS:
        return True

    return False


def post_alert():
    headers = {'content-type': 'application/json; charset=UTF-8'}
    now = datetime.today().astimezone(tz.gettz('Asia/Tokyo')).strftime('%Y/%m/%d %H:%M')
    payload = {
                    "text": "侵入者ヲ検知シマシタ!:wolf:",
                    "channel": "town-square",
                    "attachments": [
                        {
                            "text": "**Time:** "+now,
                            "mrkdwn_in": ["text"]
                        }
                    ]
              }
    response = requests.post(WEBHOOK_URL, data=json.dumps(payload), headers=headers)
    res = {
        "status" : str(response.status_code),
        "mesage" : response.content.decode("utf-8")
    }
    return json.dumps(res)


if __name__ == "__main__":
    try:
        while True:
            if GPIO.input(GPIO_PIN) == GPIO.HIGH:
                if is_alert_stopped() == False:
                    post_alert()
                sleep(10)
    except Exception as err:
        print(str(err))
        sys.exit(1)
    finally:
        GPIO.cleanup()

```

このpythonプロセスを`systemd`を使ってデーモンプロセスとして登録するために、`ban-ken-johnny.service`ファイルを用意します。

```text:ban-ken-johnny.service
[Unit]
Description=Ban-Ken Johnny
Requires=network.target

[Service]
Type=simple
ExecStart=/opt/ban_ken_johnny.py
Restart=always

[Install]
WantedBy=multi-user.target
```
この`ban-ken-johnny.service`を`/etc/systemd/system`直下に置き、`ban-ken-johnny.py`は、serviceファイルの`ExecStart`で指定しているように、`/opt`直下に置きます。
準備ができたらコマンドを実行して、`ban-ken-johnny.service`を起動します

```sh
#rootにpython-dateutilをインストール
$ sudo pip3 install python-dateutil
$ sudo systemctl daemon-reload
$ sudo systemctl start ban-ken-johnny.service
#OSが起動したらプロセスも自動起動する様にする
$ sudo systemctl enable ban-ken-johnny.service
```

プロセスのステータスを確認してみます。

```sh
$ sudo systemctl status ban-ken-johnny.service
● ban-ken-johnny.service - Ban-Ken Johnny
   Loaded: loaded (/etc/systemd/system/ban-ken-johnny.service; enabled; vendor preset: enabled)
   Active: active (running) since Wed 2019-12-12 21:20:20 JST; 8s ago
 Main PID: 3010 (ban_ken_johnny.)
    Tasks: 1 (limit: 2200)
   Memory: 11.7M
   CGroup: /system.slice/ban-ken-johnny.service
           └─3010 /usr/bin/python3 /opt/ban_ken_johnny.py

12月 12 21:20:20 raspberrypi systemd[1]: Started Ban-Ken Johnny.
```
ちゃんと動いてますね🐺
あとは人感センサーが反応したら、ちゃんと通知がくるか確認するだけです!

![notification-test.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212819/ceeb4e66-9fe7-8e7d-323e-6dd153a2d694.jpeg)


<img width="375" alt="B6ED2F5E-2211-4236-B674-9E0E2C598BEC.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212819/0798fcb3-489c-2d32-db63-2afa587b75a1.png">

無事動作しました！！
ちゃんと`ON/OFF`にしたがって、通知を飛ばしてきてくれます！！
ヤッタゼ！！

これで「Ban-Ken Johnny」は完成です。
お手軽と言いながら、地味に時間かかっちゃいました...
これで家の留守中も安心です😄


### 編集後記
完成して使い始めたは良いものの、なぜか２分置きに`GPIO`から`input`を検知してしまうという不具合が発生しました。
そのため２分置きに`「侵入者ヲ検知シマシタ！🐺」`と送信してきます。
鬼メッセージ送信機へと成り下がってしまったジョニーをどうにかして直したいのですが、ラズパイ初心者の私にはお手上げ状態です...
アドベントカレンダーの締め切りもあったので、[すぐに届くセンサー](https://www.amazon.co.jp/gp/product/B081V2BMNZ/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1)をAmazonで選んで購入したのですが、そのセンサーが悪かったのかもしれません...
今見たら誰も評価してないし...

一応[GitHub](https://github.com/hiwm0126/Ban-Ken-Johnny)にも公開しているので、参考までに...
トリアエズ有識者ノ皆サマ、是非オ助ケヲ！！