著者:米田 聡
シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。最終回は、前回作成したスクリプトによって記録された温度と湿度をグラフ化して、Webブラウザで閲覧可能にします。
シェルスクリプトマガジン Vol.67は以下のリンク先でご購入できます。

図1 サンプルのHTMLテンプレートファイル(~/webapp/templates/hello.html)
| 1 2 3 4 5 6 7 8 9 10 | <!DOCTYPE html> <html lang="ja"> <head>   <meta charset="UTF-8">   <title>こんにちは世界</title> </head> <body>     <div>{{ text }}</div> </body> </html> | 
図2 サンプルのWebアプリケーションスクリプト(~/webapp/hello.py)
| 1 2 3 4 5 6 7 8 9 10 11 | from flask import Flask, request, render_template app = Flask(__name__) @app.route('/' , methods=['GET','POST']) def index():   message = 'Hello, World'   return render_template("hello.html", text=message) if __name__ == '__main__':   app.run(host='0.0.0.0', port=5000) | 
図4 トップページのテンプレートファイル(~/webapp/templates/index.html)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <!DOCTYPE html> <html lang="ja"> <head>   <meta charset="UTF-8">   <title>温度・湿度グラフ化ツール</title> </head> <body>   <div>グラフ化する日時の範囲を指定してください。</div>   <form action="graph" method="post">     <div>開始日時</div>     <input id="startdt" type="datetime-local" name="startdt"     min="{{ mindt }}" max="{{ maxdt }}" required>      <div>終了日時</div>     <input id="enddt" type="datetime-local" name="enddt"     min="{{ mindt }}" max="{{ maxdt }}" required>      <div><input type="submit" value="実行"></div>   </form> </body> </html> | 
図5 テンプレートファイルからトップページを作成するスクリプト(~/webapp/app.py)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | from flask import Flask, request,render_template import sqlite3 import datetime DBNAME="weather.sqlite3" app = Flask(__name__) @app.route('/') def index():   conn = sqlite3.connect(DBNAME)   cur = conn.cursor()   sql = "SELECT min(dt) from bme"   cur.execute(sql)   conn.commit()   res = cur.fetchone()   m = datetime.datetime.fromisoformat(res[0])   mindt = m.strftime("%Y-%m-%dT%H:%M")   sql = "SELECT max(dt) from bme"   cur.execute(sql)   conn.commit()   res = cur.fetchone()   m = datetime.datetime.fromisoformat(res[0])   maxdt = m.strftime("%Y-%m-%dT%H:%M")   conn.close()   return render_template("index.html", maxdt=maxdt, mindt=mindt) # ここに後でグラフ表示ページの関数を追加する if __name__ == '__main__':   app.run(host='0.0.0.0', port=5000) | 
図8 グラフを表示するページのテンプレートファイル(~/webapp/templates/graph.html)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <!DOCTYPE html> <html lang="ja"> <head>   <meta charset="UTF-8">   <title>グラフ</title> </head> <body>   <canvas id="bmeChart"></canvas>   <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.js"></script>   <script>     var elm = document.getElementById("bmeChart");     var bmeChart = new Chart(elm, {       type: 'line',       data: {         labels: [           {% for d in data %}              '{{ d[0] }}',           {% endfor %}         ],         datasets: [           {              label: '気温(度)',              data:[                 {% for d in data %}                {{ d[1] }},                {% endfor %}              ],              borderColor: "rgba(255,0,0,1)",              backgroundColor: "rgba(0,0,0,0)",            },            {              label: '湿度(%)',              data: [                {% for d in data %}                {{ d[2] }},                {% endfor %}              ],              borderColor: "rgba(0,255,0,1)",              backgroundColor: "rgba(0,0,0,0)",                                   },         ],       },       options: {       },     });   </script> </body> | 
図9 app.pyに追加する関数
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @app.route('/graph', methods=['POST']) def graph():   if request.method == 'POST':     startdt = datetime.datetime.fromisoformat(request.form['startdt'])     enddt = datetime.datetime.fromisoformat(request.form['enddt'])     conn = sqlite3.connect(DBNAME)     cur = conn.cursor()     sql = "SELECT dt,temp,hum from bme where dt < " + "'" + enddt.strftime("%Y-%m-%d %H:%M:%S") + "' and dt > '" + startdt.strftime("%Y-%m-%d %H:%M:%S") + "'"     cur.execute(sql)     conn.commit()     res = cur.fetchall()     conn.close()     # データ件数が200件以上なら100件台になるよう抑える     if len(res) > 200:       p = int(len(res) / 100)       res = res[::p]   return render_template("graph.html", data=res) |