2014年2月16日日曜日

Rapsberry Piで気温のログ+Google Chartsでグラフ化

以前「気温をつぶやくRaspberry Pi」の記事で、温度センサによる室温測定と、自動でのTwitter投稿をやってみました。cronで30分ごとに定期的に温度を測り、ツイートするようにしたわけですが、せっかく定期実行しているので、温度ログを残すようにしてみました。ログといっても、淡々と羅列された数字を眺めても何も面白くないので、可視化します。

可視化、つまり温度グラフを描画すること、これはいろいろ方法があると思います。たとえば何かの言語の2D描画ライブラリを使って、一つひとつの温度ログを直線で繋いでいくとか。でも、この方法は結構面倒です。横軸はいつからいつまで表示するか、縦軸の温度範囲はどうすれば見やすいか、軸そのものや目盛り、ラベルはどうするか、考えるべきことがたくさん。これらをすべて考慮して実装しても、得られるのは静的な画像が一枚。インタラクティブなUIを作って、拡大縮小なんて、さらに手間がかかります。

そこで今回はGoogle Chartsを使ってみます。 Google Chartsは、温度変化のプロットに適した折れ線グラフのほか、円グラフ、棒グラフなど実に多様なグラフのプリセットを備えたWebベースのデータ可視化ツールです。JavaScriptで利用可能で、拡大縮小などのインタラクティブなUIを生成してくれます。しかも、フリー!Google先生、太っ腹。

気温取得スクリプトにログ機能を追加

まずは前回作った温度測定スクリプトにログ機能を追加します。本来は時刻と気温をシンプルにCSV形式などで記録したほうが汎用性は高いのですが、今回はGoogle Chartsに渡す前提で、HTMLファイルに埋め込むJavaScriptの1行分としてあらかじめ整形してテキストファイルに書き込んでしまいます。
$ vim temperature.py
#!/usr/bin/env python
#!/usr/bin/env python

import os
import os.path

# Get temperature from the sensor
hex_temp = os.popen("/usr/sbin/i2cget -y 1 0x48 0x00 w").read()
hex_temp = hex_temp[4:6] + hex_temp[2:4]
dec_temp = int(hex_temp, 16) / 8 * 0.0625 # 計算結果を変数に入れるよう修正

# ここから下を追加

# Print temperature
print int(dec_temp)

log_file = '/home/pi/path/to/temp_log.txt'
lines = []
if os.path.exists(log_file):
    with open(log_file) as f:
        lines = f.readlines()
        while len(lines) > 20000:
            lines.pop(0)

str = "          [new Date(" + os.popen("date +%Y,").read().rstrip() + os.popen("echo \"$(date +%m) - 1\" | bc").read().rstrip() + os.popen("date +,%d,%H,%M,%S").read().rstrip() + "),  " + str(dec_temp) + "],\n"

lines.append(str)
with open(log_file,'w') as f:
    f.writelines(lines)
ログファイル(temp_log.txt)の中身は、下記のような感じになっています。new Date()はDateオブジェクトのコンストラクタです。monthのみ、1月を0として数え始める点に注意が必要です。上記のPythonスクリプト内ではos.popen()関数でシェルのdateコマンドを読んで日付を取得していますが、月だけ-1しています。
          [new Date(2014,1,05,04,30,01),  23.5],
          [new Date(2014,1,05,05,00,02),  22.75],
          [new Date(2014,1,05,05,30,02),  22.1875],
          [new Date(2014,1,05,06,00,01),  21.75],
          [new Date(2014,1,05,06,30,02),  21.4375],
          [new Date(2014,1,05,07,00,01),  20.75],
          [new Date(2014,1,05,07,30,02),  20.625],
          [new Date(2014,1,05,08,00,02),  21.625],
          [new Date(2014,1,05,08,30,01),  21.6875],
          [new Date(2014,1,05,09,00,02),  21.125],

HTML出力スクリプト

表示用のHTMLを生成するスクリプトを作成します。上記スクリプトで出力したログ行をファイルから読み込んで展開します。ベースとなるHTMLは公式のサンプルなどを参考に。
$ vim write_html.py
#!/usr/bin/env python

import os
import sys

argvs = sys.argv
argc  = len(argvs)
html_path = ''
if(argc == 2):
    html_path = argvs[1] + os.sep

lines = ['']

lines.append("<html>\n")
lines.append("  <head>\n")
lines.append("    \n")
lines.append("    <script src="http://www.google.com/jsapi" type="text/javascript"></script>\n")
lines.append("    <script type="text/javascript">\n")
lines.append("      google.load('visualization', '1', {'packages':['annotatedtimeline']});\n")
lines.append("      google.setOnLoadCallback(drawChart);\n")
lines.append("      function drawChart() {\n")
lines.append("        var data = new google.visualization.DataTable();\n")
lines.append("        data.addColumn('date', 'Date');\n")
lines.append("        data.addColumn('number', 'Temp');\n")
lines.append("        data.addRows([\n")

log_file = '/home/pi/path/to/temp_log.txt'
if os.path.exists(log_file):
    with open(log_file) as f:
        lines.extend(f.readlines())

lines.append("        ]);\n")
lines.append("        var chart = new google.visualization.AnnotatedTimeLine(document.getElementById('chart_div'));\n")
lines.append("        chart.draw(data, {displayAnnotations: true});\n")
lines.append("      }\n")
lines.append("    </script>\n")
lines.append("  </head>\n")
lines.append("  <body>\n")
lines.append("    <div id="chart_div" style="height: 240px; width: 700px;">
</div>
\n")
lines.append("  </body>\n")
lines.append("</html>\n")

with open(html_path + 'index.html','w') as f:
    f.writelines(lines)
で、このスクリプトを実行してHTMLを吐き出させるわけですが、本来はCGI化して、Webアクセスがあったときに動的に実行するほうがかっこいいと思います。温度測定スクリプトと同様、30分おきにcronで実行しますが、測定結果のログ書き込み→HTML生成という動作は特に同期しません。手抜きです。cronに登録したジョブ自体、表記順にスタートこそすれ、他のジョブの終了を待つわけではないらしい(参考)ので。

あ、一つのスクリプトにまとめれば話は早いですよ、もちろん。ただhtmlの出力先をユーザのhomeディレクトリ下のpublic_htmlじゃなくて、/var/wwwの下にしたくて、HTML出力はrootのcronでやる必要があるな、と思って別々のスクリプトにしちゃいました。ですのでcorntabもsudoで。
$ sudo crontab -e
0,30 * * * * /home/pi/work/temperature/write_html.py /var/www
あらかじめWebサーバ(apache2) をインストールしておく必要があります。sudo apt-get install apache2とかやればOKだと思います。たぶん。

あとは、cronが走るなり、スクリプトを手動で実行するなりすれば、所望のHTMLファイルが生成されます。ブラウザからRaspberry Piにアクセスしてやることで、Google Chartsで生成された温度グラフが表示されるはずです。


グラフを特定の期間に絞って引き伸ばして表示してやることもできます。便利。


0 件のコメント:

コメントを投稿