ラズパイで動体検知をして撮影した画像をSlackに通知する

# やりたいこと
ラズパイ・Webカメラ・hubot・Slackを組み合わせることで、Slackからの指示でラズパイに写真を撮らせてSlackに投稿することまではできた。[詳細は前回までのQiitaを参照](https://qiita.com/s_harada/items/cfa59f37f69d4be94ea8)

今回はこの仕組みに動体検知を加えることで、Slackに写真撮影の指示を出さなくても、ラズパイ側で動体検知をトリガーにして写真を撮影し、撮影した写真をSlackに通知させたい。
また、ラズパイからSlackへの動体検知の通知はhubotから行わせたいという点もポイントである。

![WS000074.JPG](https://qiita-image-store.s3.amazonaws.com/0/329353/51193722-1d6f-3337-5f6f-d91036047ec2.jpeg)

# ハードウェア
* Raspberry Pi 2 Model B
* Logicool Webcam C270

# ソフトウェア
* hubot
* motion

# 前提
* ラズパイにWebカメラを接続して写真を撮影できること
* ラズパイにhubotをインストールしてセットアップが済んでいること
* Slackにラズパイのhubotを連携させていること
* Slackをトリガーにしてラズパイで写真を撮影するところまで実現できていること[（前回までの流れを参照）](https://qiita.com/s_harada/items/cfa59f37f69d4be94ea8)

# ラズパイの環境構築
## motionのインストール
以下のコマンドでラズパイにmotionというソフトウェアをインストールする。

[About Motion](https://motion-project.github.io/)

```
sudo apt-get install -y motion
```

## motionの設定
motionのインストールが終わったら、motionの設定ファイル(/etc/motion/motion.conf)を書き換える。
※管理者ユーザでないと上書きできないのでsudoで開く

```
$ sudo nano /etc/motion/motion.conf
```

### motionの自動起動OFF
デフォルトでは自動起動がONになっている場合もあるので、OFFになっているか確認。

```motion.conf
daemon off
```

### 動画撮影機能OFF
動画の撮影はしないので、動画出力はOFFにしておく。

```motion.conf
ffmpeg_output_movies off
```

### ファイル書き出し先の指定
motionで撮影した写真の保存先を指定する。
※環境によって異なると思うので好きな場所を指定

```
target_dir /home/pi/Pictures/motion
```

## 動作確認
### motionの起動
motionを起動して写真が撮影されるか確認する。

```
$ sudo motion -c /etc/motion/motion.conf
```

### 撮影された写真
カメラに手をかざしただけでめっちゃ撮影されている。。。
![WS000086.JPG](https://qiita-image-store.s3.amazonaws.com/0/329353/593dd47a-2f0e-0471-2ead-2561516071fa.jpeg)

カメラが監視してる画像に変化が起きる度に撮影しているので間違った動作ではないが、これでは人が通りがかるだけで何枚も写真が撮られて、Slackに激しく通知されそう。。。

### motionの停止
`Ctrl+C`で停止する。

## 改善その１：撮影する写真の数を抑えたい
上記の動作確認を踏まえて、motion.confの設定を改善してみる。
まずは動体検知をしたときに大量の写真が撮影されてしまうのをどうにかする。

### ファイル書き出し先の変更
motionの設定によってはものすごい数の画像ファイルが作成されるので、画像の出力先がSDカードのままだとSDカードが劣化してしまう恐れがあるらしい。
なのでラズパイのtmpメモリ領域に出力するように変更する。

```motion.conf
target_dir /tmp/motion
```

### 写真撮影の設定を変更
写真撮影はデフォルトでは`on`になっているが、これでは動体検知をした全フレームで写真を保存されてしまう。
この設定を`best`に変更すると最も大きく変化したフレームのみ画像ファイルとして保存してくれるようになる。

```motion.conf
output_pictures best
```

ただし、この設定を変えただけだと画像は大量に保存されなくなるが、画像の保存まで妙に時間がかかるようになる。
そのため、`event_gap`でイベントの間隔を狭める必要がある。

### イベントの間隔を設定
動体検知が起きてから、次の動体検知をするまでの間隔を設定する。
デフォルトでは60になっているが、このままだと60秒に1枚しか写真が撮影されない。
（動体を検知してから、60秒間で一番大きく変化したフレームを画像ファイルとして保存するので、動体検知から画像保存まで60秒は時間がかかる）

それほどリアルタイムでなくてもいいのであれば60のままでもいいし、もう少し早く画像を保存してほしいのであればもう少し時間を縮めてもよいと思う。
今回は10秒に設定してみた。

```motion.conf
event_gap 10
```

## 改善その２：写真を撮影したらhubotに通知してほしい
動体検知をしたら写真を撮影するところまでできたが、これだけだと画像が保存されるだけでSlackには通知できない。
なので、次は写真撮影が済んだらラズパイに常駐させているhubotに通知するようにする。
ラズパイにhubotを常駐させる方法は[過去の投稿](https://qiita.com/s_harada/items/c10b1322daec5521b261)を参照。

motionには動体検知で画像を保存したときに任意のシェルスクリプトを実行できるオプション`on_picture_save`が用意されているので、これを利用する。

### hubotでPOST-APIを提供するスクリプトを作成
hubotにはHTTPリクエストを受け付けてチャットに発言する機能があるので、この機能を利用して動体検知で撮影した写真をSlackにアップロードしてもらう。
hubotを通さず、シェルスクリプトを利用してSlack APIで直接画像をアップロードする方法もあるが、せっかくラズパイにhubotを常駐させているのならhubotから通知してもらう方がスマートだと思う。

hubotは`robot.router.post`を使用することでPOSTリクエストを受け付けることができるのでこれを利用する。

```api_upload_photo.coffee
request = require "request";
fs = require "fs";
path = require "path";

module.exports = (robot) ->
    robot.router.post "/api/v1/upload-photo", (req, res) ->
        # リクエストが空なら何もせずにレスポンスを返す        
        if !req.body
            res.status(500).send("エラーが発生しました")
        else    
            roomName = req.body.room_name
            filePath = req.body.file_path

            # リクエストで指定された写真をSlackへアップロードする
            api_url = "https://slack.com/api/"
            options = {
                token: process.env.HUBOT_SLACK_TOKEN,
                filename: path.basename("#{filePath}"),
                file: fs.createReadStream("#{filePath}"),
                channels: roomName
            }
            request.post {url:api_url + 'files.upload', formData: options}, (error, response, body) ->
                if !error && response.statusCode == 200
                    res.status(200).send("写真投稿完了")
                else
                    res.status(500).send("エラーが発生しました")
```

### hubotに向けてPOSTリクエストを実行するシェルスクリプトの作成
motionから直接POSTリクエストを出すことはできないので、hubotにPOSTリクエストを送るシェルスクリプトを作成する。
curlコマンドでPOSTリクエストを送るにはいろいろな書き方があるようだが、以下のページを参考にさせていただいた。

[curlコマンドによるデータ送信あれこれ](https://www.wagavulin.jp/entry/2015/10/18/060938)

以下の情報は再利用性を高めるためにシェルスクリプトには書かず、引数で渡すようにしている。

* Slackのチャンネル名（ルーム名）
* Slackにアップロードするファイルのフルパス
* hubotのPOST-APIのURL

```post_photo.sh
#!/bin/bash

room_name_val=${1}
file_path_val=${2}
url=${3}

echo "room_name: "${room_name_val}
echo "file_path: "${file_path_val}
echo "url: "${url}

curl --data-urlencode "room_name=${room_name_val}" --data-urlencode "file_path=${file_path_val}" $url
```

### motion.confの設定を変更
`on_picture_save`に画像保存時に実行するシェルスクリプトの名前を指定する。
このとき、`%f`には保存した画像ファイルのフルパス名が入っているので、シェルスクリプトに引数として渡す。
シェルスクリプトは以下の3つを引き数として受け取るようにしていたので、ここで実際の値を指定しておく。

* Slackのチャンネル名（ルーム名）
* Slackにアップロードするファイルのフルパス（`%f`を指定）
* hubotのPOST-APIのURL


```motion.conf
# Command to be executed when a picture (.ppm|.jpg) is saved (default: none)
# To give the filename as an argument to a command append it with %f
; on_picture_save value
on_picture_save sh /home/pi/hubot/pibot/sh/post_photo.sh [Slackのチャンネル名] %f [hubotのPOST-APIのURL]
```

### 動作確認
ラズパイでmotionを起動し、カメラの前に手をかざすなどして動体を検知させる。
動体検知に合わせて、シェルスクリプトが実行されること（シェルスクリプト内に`echo`を仕込んでおけばわかりやすい）、さらにhubotからSlackに画像の投稿が行われることを確認する。

![WS000091.JPG](https://qiita-image-store.s3.amazonaws.com/0/329353/e9f70299-ccae-237e-0987-ebdd1efd30bb.jpeg)


![WS000090.JPG](https://qiita-image-store.s3.amazonaws.com/0/329353/35c7707e-89d4-d2d2-2725-cd38083429b5.jpeg)