Raspberry Piに繋いだセンサから取得したデータをスプレッドシートに自動記録

# 今回の目的

前回投稿した記事で、温湿度センサと気圧センサを使えるようになりました。

https://qiita.com/kohey0701/items/9627746a277e70fd8039

折角なので、より実用的なシステムにするべく試行錯誤してみましょう。

# どんな機能を実装する？
某有名アウトドアウォッチの気圧計の機能を参考に実装する機能を考えます。原文のままはアレなので要約しながら見て行きましょう。
> 気圧を連続したグラフで表示。気圧変化の傾向がわかり、天候の予測を可能にする。グラフが下降気味であれば低気圧や台風の接近などの天候悪化が推測でき、上昇気味であれば天候の回復が見込まれる。
また、過去の気圧傾向を分析して気圧の急下降や、上昇が下降に転じるなど、注目すべき変化があった場合に、気圧急上昇/急降下、高気圧/低気圧通過をグラフィックで表示する。

なるほど、天気の変化を予測できるようです。気圧の変わり目なんかが分かればルアーフィッシングの戦略を考えるのにも利用できそうですね。

実際に機能として落とし込むとなると・・・

1. 現在の気圧を照会する
1. 直近2時間のデータ推移をグラフで表示する
1. 急な気圧変化があった場合に通知する
1. 気圧の変わり目(上昇傾向→下降傾向、又はその逆)を通知する
1. 直近1時間の変化量を確認出来る

こんなところでしょうか。
データベースが必要になりますし、出先ですぐに確認したり、通知を受け取ったりできなければいけません。
もちろんハードは常時稼働です。これらの条件を元に、どのように作成するか考えてみましょう。

# システム構成を考える

リアルタイム更新されるグラフを作りたいので、スプレッドシートをデータベース代わりにしてデータを記録します。
通知と言えばちょうど[先日LINEBOTを作ってみた](https://qiita.com/kohey0701/items/e86c76f3779cf03621db)ので、LINEBOTをインターフェースとしてユーザ(私)への通知と欲しいデータを要求出来るようにします。

- Line Messaging API(LINEBOT)
- Sheets API(スプレッドシート操作)
- Raspberry Pi(簡易サーバ、センサ各種のコントロール)

これらを組み合わせて作ってみましょう！

# 今回のゴール
システムの完成まで書き切ろうと思ったのですが、ボリュームがえらいことになってしまいそうだったので、切り分けて記事にしていこうと思います。

今回はラズパイからスプレッドシートに、センサのデータを記録するまでを記事にしたいと思います。

## スプレッドシートをPythonで操作出来るようにする

今回はスプレッドシートを操作するAPIを利用しますが、前提として**GCP**こと**Google Cloud Platform**を利用出来るようにしなければいけません。
私も初めて触れるのですが、概念が分かりづらくて大変でした。今回のように「スプシをPythonで操作してぇ」みたいに目的を決めれば理解しつつ進められますね。

https://qiita.com/y_a_m_a/items/b4eeb6079e14ee58d737

私はこちらの記事を参考にさせて頂きました。GCPの登録から該当API利用までの流れ、スプレッドシートの読み書きの段階までわかりやすくまとめて頂いています。

今回使用するライブラリ

```
pip3 install gspread
```
を忘れずに。

実際にコードを書いてテストし、この段階でPythonによるスプレッドシートの読み込み、書き込みが出来るようになっていればOKです。

# ラズパイからスプレッドシートにデータを記録
ではキモとなる機能を実装していきましょう！
まずは定期的にデータをスプレッドシートに記録するプログラムを作成します。
Sheets API のリクエスト制限もありますし、あんまり大量にデータ取っても扱い辛いので5分毎に記録を取ってみましょう。


### まずはimport！
```python:sheet_update.py

import gspread
from oauth2client.service_account import ServiceAccountCredentials
import json
import smbus
import RPi.GPIO as GPIO
import dht11
import time
import datetime


file = open("info.json", "r")
info = json.load(file)

SPREADSHEET_KEY = info["SPREADSHEET_KEY"]

scope = ["https://www.googleapis.com/auth/spreadsheets",
         "https://www.googleapis.com/auth/drive"]
credentials = ServiceAccountCredentials.from_json_keyfile_name(
    "hoge_credential.json", scope)
gc = gspread.authorize(credentials)

# スプレッドシートインスタンス
worksheet = gc.open_by_key(SPREADSHEET_KEY).sheet1


```
先ずは必要なモジュールをimportしつつスプレッドシート操作を有効化します。
センサ用のモジュールなどもここでimportしておきましょう。

```python:sheet_update.py

# 気圧センサ各レジスタアドレス
ADDR = 0x5C
CTRL_REG1 = 0x20
PRE_OUT_XL = 0x28
# 書き込みデータ
WRITE_REG1 = 0x90
MASK_DATA = 0x80

GPIO.setwarnings(True)
GPIO.setmode(GPIO.BCM)

# 気温センサインスタンス
instance = dht11.DHT11(pin=18)

# 気圧センサ初期化
bus = smbus.SMBus(1)
bus.write_byte_data(ADDR, CTRL_REG1, WRITE_REG1)

```
続いてセンサ類の初期化を行います。詳細は[前回の記事](https://qiita.com/kohey0701/items/9627746a277e70fd8039)を参考に。

### 欲しいデータをGetせよ
気温、気圧のデータをGetする関数を作成します。まずは気温データから。

```python:get_temp_disconfort
def get_temp():
    result = instance.read()
    if result.is_valid():
        temp = result.temperature
        humidity = result.humidity
        print("Temperature: %-3.1f C" % result.temperature)
        print("Humidity: %-3.1f %%" % result.humidity)
    return temp, humidity


def disconfort_index(temp, humidity):
    temp = float(temp)
    humidity = float(humidity)
    return 0.81*temp + 0.01*humidity*(0.99*temp-14.3)+46.3 #不快指数を算出

```

気温湿度の生データだけでは寂しいので`disconfort_index`関数で不快指数もGetしておきます。体感的な「熱い」「寒い」が数値化できるので便利ですね。

続いて気圧データをGetします。

```python:get_pressure
def get_pressure(bus, values):
    data = bus.read_i2c_block_data(ADDR, PRE_OUT_XL | MASK_DATA, 3)
    pressure = (data[2] << 16 | data[1] << 8 | data[0]) / 4096.0
    print("%.2f hpa" % pressure)
    return pressure
```
こちらも前回作った気圧センサのデータ取得処理を関数にまとめます。
`smbus`のメソッドを叩くので冒頭の初期化処理で生成したインスタンスを引数にしておきましょう。

## 取得したデータを記録しよう！

ここまででGet出来たデータを渡してスプレッドシートに書き込む関数を作成します。

```python:sheet_update
def sheet_update(worksheet,temp, humidity, disc, pressure):
    dt = datetime.datetime.now()
    dt = dt.replace(microsecond=0)
    values = worksheet.get_all_records()
    worksheet.update(f"A{len(values) + 2}:E",[[str(dt), temp, humidity,  int(disc), round(pressure, 1)]])

```

`values`変数に`get_all_records()`を使用しています。スプレッドシートのヘッダがキーとなった辞書型で返してくれます。

`f"A{len(values) + 2}:E"`でA～E列のヘッダと既存データの最終行を除いた次の行に、各データを書き込みます。次の空白にデータを書き込む処理という事ですね。

ではこれを5分おきに実行する処理を書いて実行してみましょう。

```python:main
def main():
    print("System enable ...")
    time_buff = 0
    count = 0
    while True:
        # 1秒間待機する
        while(time.time() - time_buff < 1):
            pass
        # 待機完了したらそのタイミングを変数に記録する
        time_buff = time.time()
        # 処理を通す度に(今回は1秒毎に)カウントをプラス1
        count += 1
        # countが300になったら(5分経ったら)以下の処理を実行する
        if (count == 1) or (count % 300) == 0:
            pressure = get_pressure(bus)
            temp,humidity = get_temp()
            disc = disconfort_index(temp, humidity)

            sheet_update(worksheet,
                         temp, humidity, disc, pressure)

if __name__ == "__main__":
    main()

```
ポーリングの考え方で5分おきにデータを記録する処理を実装します。
シェルスクリプトで5分おきに実行する手法も考えましたが、後に実装する機能にローカル変数で前回値取りたい場面があったりするので今回はこちらの方法を選択しました。

この方法だと例えば、データを記録するタイミングでセンサを叩くより、データを記録するまでに10秒おきぐらいにセンサを叩いてリストに格納、データを記録するタイミングでそれらの平均値を取るようにしておく、といった流れにすればデータの精度がアップします。
人感センサのような比較的早い周期で回したいセンサを追加したい時なんかも良いですね。

今回はそこまで制度は求めないので一旦これで実行してみましょう。

**実行結果**

![jikkou.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653352/bc131823-e683-7c12-df20-d7ebcddc30e8.jpeg)


無事にデータの記録が出来ました！
自動でどんどん溜まっていくので見ていて面白いです！
`Google Drive`に保存されているのでいつでもどこでもチェックできます。

# おわりに
今回はスプレッドシートをPythonとライブラリを使って操作出来るようになりました。
常時稼働させるソフトはサーバにデプロイしたり色々手間がありますが、こちとらラズパイなので手元で常時稼働させられて、手間いらずかつロマンありますね。

次は今回のプログラムにLINEBOTを組み合わせて通知を送ったり、データを取得したり出来るようにしていきます。