RaspberryPi Zeroを使った室温監視（& Slack通知）

室温を監視し、設定温度を超えた際にslackで通知するものを作ったのでメモ。

使ったもの。

- Raspberry Pi Zero W
- [USB Thermometer](https://www.amazon.co.jp/OEM-USB%E6%B8%A9%E5%BA%A6%E8%A8%88-USB-thermometer-528018/dp/B004FI1570)

# 室温監視
## raspbianのインストール
NOOBS LiteでRaspbian Liteをインストール。
https://www.raspberrypi.org/downloads/noobs/
特段気をつけることはなく、インストーラに従ってインストールする。
[この辺り](https://raspida.com/raspixnoobs)参考。

## Thermometerの設定
Amazonから購入したが、ファームウェアのバージョンが２種類あり、どちらがやってくるかは運次第の模様。
今回手元に届いたのは、`413d:2107` というバージョンのものだったので、ここから下は同バージョンで動作確認できた手順です。
（他のバージョンではgitからライブラリを持ってくるだけで動くとか動かないとか...）

### HIDAPIのインストール
USBと通信するため、HIDAPIライブラリをインストールする。
https://github.com/signal11/hidapi

READMEにある通り、下記ライブラリが必要になるのでインストールする。

```bash
# まずは依存ライブラリのインストール
$ sudo apt-get install libudev-dev libusb-1.0-0-dev libfox-1.6-dev
$ sudo apt-get install autotools-dev autoconf automake libtool

# HIDAPIをインストール
$ git https://github.com/signal11/hidapi.git
$ cd linux/
$ make -f Makefile-manual
```

### TEMPeredのインストール
いよいよ、thermometerから温度を取得するためのTEMPeredライブラリをインストールする。

```bash
$ git clone https://github.com/hughesr/TEMPered
$ cd TEMPered
# 上手くいかなかったのでissues見てたら最新リビジョンはMacに対応っぽかったので、
# Linuxに対応しているリビジョンまで戻してmake
# https://github.com/edorfaus/TEMPered/issues/51
$ git checkout hack-413d-2107
$ git reset --hard 75aa1e2
$ cmake .
$ make
```

これで温度が取得できるようになった。

```bash
$ sudo utils/tempered
/dev/hidraw1 0: temperature 25.37 °C
/dev/hidraw1 1: Failed to get the temperature: Not enough data was read from the sensor.
/dev/hidraw1 1: no sensor data available
```

## 室温監視スクリプト作成
温度が取れるようになったので、下記要件のスクリプトを作成。

- 5分おきに温度を取得し、DBに格納する


※下記ソースは2019/8/18時点のもので、この後の機能追加及びリファクタリングで変更される可能性あり。

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

# TEMPeredで温度を取得し、awkで温度のみを切り出す
TEMPER=`sudo /home/pi/TEMPered/utils/tempered | head -n 1 | awk '{ print $4 }'`

# 温度を引数にDB登録、slack通知のpythonを起動
cd /home/pi/temperature_monitor/
python3 handle_temperature.py $TEMPER
```

monitor.shをcronに設定し、5分おきに起動するようにする。

# Slack連携

下記の条件でslackに通知を送る。

- 5回連続気温の取得に失敗したらslackで通知する
- 気温が30度を上回ったらslackで通知する

SlackのAPIはいくつか種類があるみたいで、今回はメッセージを送信したいだけなので、Incoming Webhooksを使用。

下記URLからwebhookのエンドポイントを作成。
https://api.slack.com/apps?new_app=1

作成すると、 `https://hooks.slack.com/services/********/*******/**********` の様なエンドポイントが与えられる。
ここに対して正しいフォーマットでPOSTしてやればslackに投稿ができる。

スクリプトはpython3で作成。

```handle_temperature.py
import logging
import configparser
import sys
import datetime
import sqlite3
from contextlib import closing
from messaging_slack import notice 

config = configparser.ConfigParser()
config.read('config')

SUCCESS = config['STATUS']['SUCCESS']
ERROR = config['STATUS']['ERROR']
COUNT_DEFAULT = config['ERROR_COUNT']['DEFAULT']
COUNT_LIMIT = config['ERROR_COUNT']['LIMIT']
DB = config['DB']['file']
log_file = config['LOG']['file']

THRESHOLD = 33.0

NOTICE_MESSAGE = "<!channel> 室温が {} 度になりました。"
ERROR_MESSAGE = "<!channel> デバイスに接続できません。"

logger = logging.getLogger(__name__)
fmt = "%(asctime)s %(levelname)s :%(message)s"
logging.basicConfig(level=logging.DEBUG, format=fmt, filename=log_file)


def error_check(flg):
    if flg == SUCCESS:
        update_error_count()
    else:
        error_count = get_error_count()
        error_count += 1

        if error_count == COUNT_LIMIT:
            notice(ERROR_MESSAGE.format(t))
            logger.error(ERROR_MESSAGE.format(datetime.datetime.now()))

        update_error_count(error_count)

def get_error_count():
    with closing(sqlite3.connect(DB)) as conn:
        cursor = conn.cursor()
        cursor.execute("select count from error_counts")
        res =  cursor.fetchone()
        conn.close()
        return res[0]

def update_error_count(error_count=COUNT_DEFAULT):
    with closing(sqlite3.connect(DB)) as conn:
        cursor = conn.cursor()
        sql = "update error_counts set count = ?, updated_at = ?"
        cursor.execute(sql, (error_count, datetime.datetime.now()))
        conn.commit()
        conn.close()

def str_add(s, num):
    i = int(s)
    i += num
    return str(i)

if __name__ == "__main__":
    args = sys.argv

    try:
        t = args[1]
        t = float(t)
        error_check(ERROR)
    except:
        error_check(ERROR)
        logger.error(ERROR_MESSAGE.format(datetime.datetime.now()))
        exit()

    if t > THRESHOLD:
        message = NOTICE_MESSAGE
        notice(message.format(t))

    with closing(sqlite3.connect(DB)) as conn:
        cursor = conn.cursor()
        sql = "insert into temperature (temperature, created_at) values (?, ?)"
        now = datetime.datetime.now()
        cursor.execute(sql, (t, now.strftime("%Y-%m-%d %H:%M")))
        conn.commit()
        conn.close()
```

```messaging_slack.py
import json
import os
import urllib.request


def notice(message):
    data = {
            'text': message,
    }
    headers = {
            'Content-type': 'application/json'
    }
    
    # slackのIncoming WebhooksのURLは環境変数に設定
    req = urllib.request.Request(os.environ['SLACK_URL'], json.dumps(data).encode(), headers)
    urllib.request.urlopen(req)
```

以上でRaspberryPi Zeroを使った室温監視が構築できた。

次は、Slackのbotを使って室温の問い合わせや室温推移のグラフを描きたい。