Raspberry Pi Zero WHを使用したSwitchBotデータの収集とグラフ作成 その2

前回の続き hemus.hatenablog.com

開発

プログラム改修

要点

  • cron で毎時 0, 15, 30, 45 分に実行するよう変更

  • sheet1 にデータを蓄積

    最終行に追加する手間がかかるため3行目に行追加するよう変更。2行目は数式が埋め込んであるためあえて3行目にしている。

  • sheet2 に最新のデータを登録

cron

0,15,30,45 * * * * /root/develop/run.sh

python

#!/usr/bin/python3

import sys
import datetime
import logging

# Switch-Botからデータ取得のためのインポート
import binascii
from bluepy.btle import Scanner, DefaultDelegate, BTLEDisconnectError

# GoogleSpreadSheetに書き込むためのインポート
import requests
import gspread
from oauth2client.service_account import ServiceAccountCredentials

logging.basicConfig(level=logging.ERROR)

# スプレッドシート
key_name = '{ jsonファイルを指定 }'
book_name = '{ Spreadsheet のファイル名 }'
sheet_name = '{ Spredsheet のシート名 }'
sheet_name2 = '{ Spredsheet のシート名2 }'

# GoogleAPIを通してスプレッドシートに接続
scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_name(key_name, scope)
gc = gspread.authorize(credentials)

# シートを取得
worksheet = gc.open(book_name).worksheet(sheet_name)
worksheet2 = gc.open(book_name).worksheet(sheet_name2)

class ScanDelegate(DefaultDelegate):
    def __init__(self, addr, name, no):
        DefaultDelegate.__init__(self)
        self.addr = addr
        self.name = name
        self.no = no
        self.processed_devices = set()  # 処理済みのデバイスの初期化

    def handleDiscovery(self, dev, isNewDev, isNewData):
        # アドレスチェック
        if dev.addr != self.addr:
            return

        # 処理済みでないかチェック
        if dev.addr in self.processed_devices:
            return

        # 主処理
        for (adtype, desc, value) in dev.getScanData():
            # 気温、湿度を読み込んでスプレッドシートに登録
            if(adtype == 22):
                servicedata = binascii.unhexlify(value[4:])
                battery = servicedata[2] & 0b01111111

                #print(self.name + "," + timestamp + "," + str(battery) + "," + str(temp) + "," + str(humid))
                self.writeSpreadsheet(self.name, timestamp, battery, temp,
                                      humid, self.no)

                # 処理したデバイスのアドレスを記録
                self.processed_devices.add(dev.addr)

                return
            elif(adtype == 255):
                madata = binascii.unhexlify(value[16:])
                humid = madata[4] & 0b01111111
                temp = (madata[2] & 0b00001111) / 10 + (madata[3] & 0b01111111)
                isOverZero=(madata[3]&0b10000000)
                if not isOverZero:
                    temp = -temp
                continue
            else :
                continue

    def writeSpreadsheet(self, name, timestamp, battery, temp, humid, no):
        # 書き込み
        # 2行目は表示書式、数式などを埋め込むためあえて3行目に追加する
        worksheet.insert_row([name, timestamp, temp, humid], 3)

        # 書き込み
        worksheet2.update_cell(1 + no, 1, name)
        worksheet2.update_cell(1 + no, 2, timestamp)
        worksheet2.update_cell(1 + no, 3, battery)
        worksheet2.update_cell(1 + no, 4, temp)
        worksheet2.update_cell(1 + no, 5, humid)

def scan(addr, name, no):
    scanner = Scanner().withDelegate(ScanDelegate(addr, name, no))
    try:
        # 指定秒BLEのスキャンを行う
        scanner.scan(20)
    except BTLEDisconnectError as e:
        pass
    except Exception as e:
        logging.error("Error during scanning: %s", e)

def main():
    global timestamp
    timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:00")

    scan("xx:xx:xx:xx:xx:x1", "名称1")
    scan("xx:xx:xx:xx:xx:x2", "名称2")
    scan("xx:xx:xx:xx:xx:x3", "名称3")

if __name__ == "__main__":
    main()

sheet1

蓄積データ。列 E,F,G はグラフ表示用に数式を設定。

sheet2

最新データ

グラフの作成

蓄積データについては気温、湿度それぞれでピボットテーブルを作ってグラフを作成する。

最新データも同様。

グラフのサイズを良い感じにし、インタラクティブに公開できるので設定、iframe を取得する。

Google ChartsをQuickにStartする  https://www.i-ryo.com/entry/2018/12/10/073520
「スプレッドシートのグラフは簡単に埋め込みできる」の項目を採用。
「Google ChartsをQuickにStart」の項目は、複数のチャートを表示することができず諦めた。

html の作成

取得した iframe を配置し、キャッシュなし、自動リロード設定。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Cache-Control" content="no-cache">
    <meta http-equiv="refresh" content="300">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>SwitchBot Meter</title>
</head>
<body style="background-color:#2c2f38;">

<iframe width="299" height="106" seamless frameborder="0" scrolling="no" src="hogehoge"></iframe>
~省略~

</body>
</html>

完成