Raspberry Piで監視カメラの動体検知時に電話通知

Raspberry Piを監視カメラとして動かすのは定番ですが
動体検知時に指定した番号へ電話通知するものはなかったのでつくってみました。


#仕様
* motionを利用して動体検知を行う
* 動体検知時に撮影してSlackへ自動投稿
* Slackへ投稿するついでに指定番号に電話をかけて知らせる
* Slackに着信ON/OFFとなる投稿。電話で通知してほしい時に着信させる


用途としては・・

自宅に子供が帰っきたことを動体検知して電話着信。
↓
「お？電話がきた」。Slackで子供の姿を確認
↓
着信OFFをSlackに投稿して動体検知しても着信させない


#環境
* Raspberry Pi Model B
* Raspbian (Kernel version 3.18)
* Asterisk 13.5.0
* Python 2.6.6

カメラは <a href="http://www.amazon.co.jp/dp/B003YUB660/">LOGICOOL ウェブカム HD画質 120万画素 C270(Amazon)</a> を使用。


#手順

##Asteriskの設定
Asteriskの設定は下記を参考
<a href="http://qiita.com/hasudon7171/items/925b9512f81cee46fbe3">Raspberry PiとAsteriskでIP電話システムの構築(その1)</a>
<a href="http://qiita.com/hasudon7171/items/b5338e8a7905fb1ad50d">Raspberry PiとAsteriskでIP電話システムの構築(その2)</a>

##motionの設定

motionをインストールします

```:Terminal
apt-get install -y motion
```

motion.confを変更します

```:Terminal
vi /etc/motion/motion.conf
```

```Ini:motion.conf
# Threshold for number of changed pixels in an image that
# triggers motion detection (default: 1500)
#threshold 1500
threshold 8000   #写真を撮影するトリガとなるしきい値を上げます

：

# Target base directory for pictures and films
# Recommended to use absolute path. (Default: current working directory)
target_dir /tmp/motion #撮影した画像の保存先を設定します

：

#撮影時にmotion.pyファイルを実行します。パラメータの%fは撮影したファイルPATH
on_picture_save python /home/hasuo/motion/motion.py %f

```

motionを実行して、カメラが動体検知して/home/tmp/に画像ファイルが生成されていれば成功です

```:Terminal
motion -c /etc/motion/motion.conf
```

##Slackの設定
<a href="https://slack.com/signin">Slackでアカウントを発行</a>します。

SlackのAPIを利用して投稿するため、トークンとチャンネルIDを確認します。
<a href="https://api.slack.com/methods/channels.list/test">https://api.slack.com/methods/channels.list/test</a>

Sign Inした状態で[Test Mode]押下でトークンとチャンネルIDを取得します。


```:URL
https://slack.com/api/channels.list?token=<トークン>&pretty=1 
```

```:channels.list実行結果
{
    "ok": true,
    "channels": [
        {
            "id": "<チャンネルID>",
            "name": "general",
            "is_channel": true,
              :
```

##画像のアップロード

SlackのAPIを利用して動体検知時にSlackへ自動的に画像投稿します。
まずはSlackへ投稿する箇所をつくります。

```py:motion.py
TOKEN      = '<トークン>'
CHANNEL    = '<チャンネルID>'

# slack API : files.upload
# 参考：https://api.slack.com/methods/files.upload
####
def upload_file(file_path, channel):
    
    with open(file_path,'rb') as f:
        param = {'token':TOKEN, 'channels':CHANNEL,}
        r = requests.post("https://slack.com/api/files.upload", params=param,files={'file':f})
    

if __name__ == "__main__":
    
    arg     = sys.argv
    file_path = arg[1]
    
    upload_file(file_path, CHANNEL)
    
```

ソースファイルに実行権限を与えて適当な画像ファイルで画像投稿できるか確認します。

```:Terminal
chmod 755 motion.py
python motion.py <画像ファイルのPATH>
```

Slackで確認すると無事画像が投稿されました。

![無題.png](https://qiita-image-store.s3.amazonaws.com/0/32122/d7b86beb-f073-2bad-5f5f-e857abb95bd5.png)


##電話通知

Asteriskが自動発信するように自動発信用のファイルを作成するようにします。

```py:motion.py
TOKEN                 = '<トークン>'
CHANNEL               = '<チャンネルID>'
CALLFILE_DIR          = '<自動発信ファイルディレクトリ>'
CALLFILE_NAME         = 'auto_call.call'    # 自動発信ファイル名
OUTGOING_DIR          = '/var/spool/asterisk/outgoing/'

# 自動発信ファイル生成 -> outgoing
####
def outgoing_call():
    
    file_str = '''#
Channel: SIP/<通知したい電話番号>@<外線発信するセクション名>
MaxRetries: 0               # コール回数は１回
RetryTime: 60               # 再度コールするまでの待ち時間(秒)
WaitTime: 30                # 30秒間コールする
Context: <extensions.confに沿ったContext設定>
Extension: <extensions.confに沿ったExten設定>
Priority: 1'''
    
    file = open(CALLFILE_DIR +CALLFILE_NAME, "w")
    file.writelines(file_str);
    file.close()
    
    os.chmod(CALLFILE_DIR + CALLFILE_NAME, 0755)
    shutil.move(CALLFILE_DIR + CALLFILE_NAME, OUTGOING_DIR)

if __name__ == "__main__":
    
    outgoing_call()

```

ソースファイルに実行権限、/var/spool/asterisk/outgoing/ に書き込み権限を与えてファイル実行で電話着信するか確認します。

```:Terminal
chmod 755 motion.py
python motion.py
```

##電話通知ON/OFF

動体検知時にSlackから get_channel_info を利用して最新の投稿を取得します。

* 投稿のテキストが「call」だったら電話通知ONにします。
* 投稿のテキストが「nocall」だったら電話通知OFFにします。


電話通知のON/OFF状態はConfigParserで設定ファイルを作成することで保持します。

もっとスマートなやり方があると思うのだけど、思いつかなかったので今回はこれで。

```py:motion.py
# coding:utf-8

import sys
import os
import shutil
import pprint
import urllib
import urllib2
import json
import ConfigParser

TOKEN                 = '<トークン>'
CHANNEL               = '<チャンネルID>'
CALL_NOTIFY_START_STR = 'call';       # 電話通知ON判定文字列
CALL_NOTIFY_END_STR   = 'nocall';     # 電話通知OFF判定文字列
INIFILE_PATH          = 'config.ini'  # 設定ファイル
SECTION               = 'motion notify'

# slack API : channels.info
# 参考：https://api.slack.com/methods/channels.info
####
def get_channel_info():

    url = "https://slack.com/api/channels.info"

    params = {'token'  : TOKEN,
              'channel': CHANNEL,
    }

    params = urllib.urlencode(params)

    req    = urllib2.Request(url)
    req.add_header('Content-Type', 'application/x-www-form-urlencoded')

    req.add_data(params)
    res     = urllib2.urlopen(req)
    body    = res.read()
    result  = json.loads(body)

    return result

# 電話通知ON/OFF設定
# slack_last_text : slackから受信した最新の投稿のテキスト部分
####
def set_call_notify(slack_last_text):

    config = ConfigParser.SafeConfigParser()

    if not config.has_section(SECTION):
        config.add_section(SECTION)

    # 設定ファイルの内容取得
    try :
        config.read(INIFILE_PATH)
        ini_set = config.get(SECTION,'enable')
    except Exception as e:
        ini_set = 'FALSE'

    if slack_last_text == CALL_NOTIFY_START_STR:
        config.set(SECTION, 'enable', 'TRUE')
        ini_set = 'TRUE'

    elif slack_last_text == CALL_NOTIFY_END_STR:
        config.set(SECTION, 'enable', 'FALSE')
        ini_set = 'FALSE'
    else:
        config.set(SECTION, 'enable', ini_set)

    config.write(open(INIFILE_PATH, 'w'))

    return ini_set

if __name__ == "__main__":

    channnels_info = get_channel_info()

    is_call = set_call_notify(channnels_info['channel']['latest']['text'])

    print is_call

```

Slackで「callon」「calloff」を投稿後に、撮影されると下記のような設定ファイルが生成されれば成功です。

```Ini:config.ini
[motion notify]
enable = FALSE
```

これらを踏まえてソースファイルを作成。

実際に動作させてみると・・・

![無題2.png](https://qiita-image-store.s3.amazonaws.com/0/32122/0717b2a0-19ed-68a1-8493-311ea43a4255.png)

カメラの前で動くだけでガンガン投稿されて、ガンガン電話がかかってくる。
もはや迷惑(笑)

「nocall」を投稿すると、Slackには写真が投稿されて電話通知はしなくなったのでこれで完成


motionやSlackに関する詳しい説明は下記を参照ください
<a href="http://qiita.com/kinpira/items/bf1df2c1983ba79ba455">はじめてのRaspberry PIで監視カメラを作ってみた。</a>



#所感
音声合成エンジン<a href="http://open-jtalk.sp.nitech.ac.jp/">Open JTalk</a>を利用して、動体検知したら何かしゃべらせると面白そう

##参考
<a href="http://qiita.com/kinpira/items/bf1df2c1983ba79ba455">はじめてのRaspberry PIで監視カメラを作ってみた。</a>