HOME → 1 Raspberry Pi → 03 作品集 → 

押されたボタンで表示するグラフの内容を変える Webアプリ

Raspberry Pi 作品集
Python + Flask + MariaDB
押されたボタンでグラフの表示内容を変える
 
Rasp Pi で計測した温度データを、折れ線グラフで時系列に分析できる機能を実装したが、更に、計測値をプロットする時間間隔と表示する期間が選択できるボタンを用意し、さらなる分析力の向上を追求。
 
ボタン表示画面で、時間間隔と表示期間を選択
 
以下、時系列での推移をリアルタイムに参照する「折れ線グラフ」を、ボタンにより内容を変更して表示した記録。
 
 

 

スポンサー リンク

 

 
 
 
 
 
1. ボタン設定の概要
 
Rasp Pi で計測した温度データを、ブラウザでリアルタイムに分析できる機能を実装したが、更に分析力を高めるべく、計測値をプロットする時間間隔と表示する期間を選択できるボタンを用意してみる。
 
Rasp Pi での温度計測は、cronで「毎時10分ごとに実行する */10 * * * *」の設定としており、計測データには10分間隔のタイムスタンプを付けて、MariaDBに蓄積している。
 
このMariaDBに保存されているデータのタイムスタンプを利用して、ボタン別に次のような内容で表示することとした。
 
ボタン --- 表示する折れ線グラフ(プロット数)
10min.:10分毎の計測値を、現在時より24時間の範囲で表示(6x24=144)
20min.:20分毎の計測値を、現在時より48時間の範囲で表示(3x48=144)
30min.:30分毎の計測値を、現在時より3日間の範囲で表示(48x3=144)
60min.:60分毎の計測値を、現在時より6日間の範囲で表示(24x6=144)
 
参考:
 
 
 
2. 押されたボタンで処理を変えるWebアプリの構成
 
Python の Flask を利用したWebアプリ「app.py」を立ち上げて、URLを入力すると「index.html」でボタンを表示し、ボタンがクリックされると該当の処理をした結果を【render】して「result.html」で表示する。
押されたボタンで処理を変えるWebアプリの構成
 
必要なパッケージ。
pip install Flask
pip install requests
 
 
ディレクトリー構成
/work         (ホーム直下に作成)
├── app.py      (Flask Webアプリ)
└── templates
  └── index.html (ボタン表示)
  └── result.html(処理結果表示)
 
 
①.app.py(Flask Webサンプルアプリ)。
from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/button_click', methods=['POST'])
def button_click():
    button_id = request.form['button_id']
    
    if button_id == 'button1':
        # ボタン1がクリックされた場合の処理
        result = "ボタン1がクリックされました。"
    elif button_id == 'button2':
        # ボタン2がクリックされた場合の処理
        result = "ボタン2がクリックされました。"
    else:
        result = "不明なボタンがクリックされました。"

    return render_template('result.html', result=result)

if __name__ == '__main__':
    app.run(debug=True)
 
②.index.html(ボタン表示サンプル)。
<!DOCTYPE html>
<html>
<head>
    <title>Flask Button Example</title>
</head>
<body>
    <form method="post" action="{{ url_for('button_click') }}">
        <button type="submit" name="button_id" value="button1">ボタン1</button>
        <button type="submit" name="button_id" value="button2">ボタン2</button>
    </form>
</body>
</html>
 
③.result.html(処理結果表示サンプル)。
<!DOCTYPE html>
<html>
<head>
    <title>Flask Button Example - Result</title>
</head>
<body>
    <p>{{ result }}</p>
    <a href="{{ url_for('index') }}">戻る</a>
</body>
</html>
 
 
 
3. MariaDBからデータを読み込む条件式の設定要領
 
Rasp Pi での温度計測は、cronで「毎時10分ごとに実行する */10 * * * *」の設定にしている為、データには下記のタイムスタンプが付けられている。
yyyy-mm-dd hh:00:ss
yyyy-mm-dd hh:10:ss
yyyy-mm-dd hh:20:ss
yyyy-mm-dd hh:30:ss
yyyy-mm-dd hh:40:ss
yyyy-mm-dd hh:50:ss
 
このタイムスタンプを利用して、MariaDBからデータを読み込む条件を設定した。
 
必要なモジュール。
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
 
 
①.10分毎の計測値を、現在時より24時間の範囲で抽出する条件式。
sql_query = "select * from meas_value where DATE_ADD(nitiji, INTERVAL 24 HOUR) > NOW()"
 
②.20分毎の計測値を、現在時より48時間の範囲で抽出する条件式。
# 過去48時間のデータの読込
start_datetime = current_datetime - timedelta(hours=48)

# SQLクエリの構築
sql_query = (
    f"SELECT * FROM meas_value "
    f"WHERE nitiji BETWEEN '{start_datetime.strftime('%Y-%m-%d %H:%M:%S')}' "
    f"AND '{current_datetime.strftime('%Y-%m-%d %H:%M:%S')}' "
    f"AND (MINUTE(nitiji) = 00 OR MINUTE(nitiji) = 20 OR MINUTE(nitiji) = 40)"
)
 
③.30分毎の計測値を、現在時より3日間の範囲で抽出する条件式。
# 過去3日間のデータの読込
start_datetime = current_datetime - timedelta(days=3)

# SQLクエリの構築
sql_query = (
    f"SELECT * FROM meas_value "
    f"WHERE nitiji BETWEEN '{start_datetime.strftime('%Y-%m-%d %H:%M:%S')}' "
    f"AND '{current_datetime.strftime('%Y-%m-%d %H:%M:%S')}' "
    f"AND (MINUTE(nitiji) = 00 OR MINUTE(nitiji) = 30)"
)
 
④.60分毎の計測値を、現在時より6日間の範囲で抽出する条件式。
# 過去6日間のデータの読込
start_datetime = current_datetime - timedelta(days=6)

# SQLクエリの構築
sql_query = (
    f"SELECT * FROM meas_value "
    f"WHERE nitiji BETWEEN '{start_datetime.strftime('%Y-%m-%d %H:%M:%S')}' "
    f"AND '{current_datetime.strftime('%Y-%m-%d %H:%M:%S')}' "
    f"AND MINUTE(nitiji) = 00"
)
 
 
 
4. 実装したプログラム
 
Python-Flask Webアプリ。
#!/usr/bin/python
# -*- coding: utf-8 -*

from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
import socket
import json
import MySQLdb #DBライブラリの読み込み
from flask import Flask, render_template, request
from multiprocessing import Process

import jusin_05

# -----------------------------------------------

# Flaskインスタンス作成
app = Flask(__name__)

# -----------------------------------------------
# 並列処理-1 DBを読んでグラフ表示

@app.route('/')
def index():

	return render_template('index.html')

@app.route('/button_click', methods=['POST'])
def button_click():

	# 2次元リストを定義
	temp_list = []

	# MySQLに接続
	connector = MySQLdb.connect(
		host = "localhost",
		db = "ondodb",
		user = "flaskpy",
		passwd = "dht22x4",
	# テーブル内部で日本語を扱うために追加
		charset = "utf8"
	)

	# カーソル取得
	cursor = connector.cursor()

	# 現在の日時を取得
	current_datetime = datetime.now()

	button_id = request.form['button_id']

	if button_id == 'button1':
        # ボタン1がクリックされた場合の処理

		#テンプレートへ挿入するデータの作成
		title = "Raspberry Pi で測定した温度の推移(10min x 24H)グラフ"

		# 過去24時間のデータの読込
		sql_query = "select * from meas_value where DATE_ADD(nitiji, INTERVAL 24 HOUR) > NOW()"

	elif button_id == 'button2':
        # ボタン2がクリックされた場合の処理

		#テンプレートへ挿入するデータの作成
		title = "Raspberry Pi で測定した温度の推移(20min x 48H)グラフ"

		# 過去48時間のデータの読込
		start_datetime = current_datetime - timedelta(hours=48)

		# SQLクエリの構築
		sql_query = (
		    f"SELECT * FROM meas_value "
		    f"WHERE nitiji BETWEEN '{start_datetime.strftime('%Y-%m-%d %H:%M:%S')}' "
		    f"AND '{current_datetime.strftime('%Y-%m-%d %H:%M:%S')}' "
		    f"AND (MINUTE(nitiji) = 00 OR MINUTE(nitiji) = 20 OR MINUTE(nitiji) = 40)"
		)

	elif button_id == 'button3':
        # ボタン3がクリックされた場合の処理

		#テンプレートへ挿入するデータの作成
		title = "Raspberry Pi で測定した温度の推移(30min x 3days)グラフ"

		# 過去3日間のデータの読込
		start_datetime = current_datetime - timedelta(days=3)

		# SQLクエリの構築
		sql_query = (
		    f"SELECT * FROM meas_value "
		    f"WHERE nitiji BETWEEN '{start_datetime.strftime('%Y-%m-%d %H:%M:%S')}' "
		    f"AND '{current_datetime.strftime('%Y-%m-%d %H:%M:%S')}' "
		    f"AND (MINUTE(nitiji) = 00 OR MINUTE(nitiji) = 30)"
		)

	elif button_id == 'button4':
        # ボタン4がクリックされた場合の処理

		#テンプレートへ挿入するデータの作成
		title = "Raspberry Pi で測定した温度の推移(60min x 6days)グラフ"

		# 過去6日間のデータの読込
		start_datetime = current_datetime - timedelta(days=6)

		# SQLクエリの構築
		sql_query = (
		    f"SELECT * FROM meas_value "
		    f"WHERE nitiji BETWEEN '{start_datetime.strftime('%Y-%m-%d %H:%M:%S')}' "
		    f"AND '{current_datetime.strftime('%Y-%m-%d %H:%M:%S')}' "
		    f"AND MINUTE(nitiji) = 00"
		)

	else:
		result = "不明なボタンがクリックされました。"

	# 条件の値を使ってクエリを実行
	cursor.execute(sql_query)

	# 結果を取得し、2次元リストの形式に変換
	records = cursor.fetchall()
	for record in records:
		temp_list.append(
		{'nitiji':record[0].strftime("%Y-%m-%d %H:%M"),
		 'temp1':record[1],
		 'temp2':record[2],
		 'temp3':record[3],
		 'temp4':record[4]}
		)

	# データベースとの接続を閉じる
	cursor.close()
	connector.close()

	return render_template('template.html', title=title, temp_list=temp_list)


# 並列処理-2 受信とDB書込み
def jusin2db():
    jusin_05.start_server()

# 並列処理-1 DBを読んでグラフ表示を起動
def db2graph(**kwargs):
    app.run(**kwargs)


if __name__ == '__main__':
    server = Process(target = db2graph, kwargs = {'host': '0.0.0.0', 'port': 5001, 'threaded': True})
    server.start()

    jusin2db()
 
ボタン表示html(index.html)。
<!DOCTYPE html>
<html>
<head>
    <title>Flask Button Example</title>

<style type="text/css">
button {
    font-size: 24px;
}
</style>

</head>
<body>
    <div style="text-align:center;">
    <hr style="height: 4px; width: 900px; background-color: blue;">
    </br>
    <div style="font-size: 200%;">
    <div>Raspberry Pi で 測定中の温度 をグラフで見る</div>
    <form method="post" action="{{ url_for('button_click') }}">
        <button type="submit" name="button_id" value="button1">10min.</button>
        <button type="submit" name="button_id" value="button2">20min.</button>
	<button type="submit" name="button_id" value="button3">30min.</button>
	<button type="submit" name="button_id" value="button4">60min.</button>
    </form>
    </div>
    </br>
    <hr style="height: 4px; width: 900px; background-color: blue;">
	<div>10min.:10分毎の計測値を、現在時より24時間の範囲で表示(6x24=144)</div>
	<div>20min.:20分毎の計測値を、現在時より48時間の範囲で表示(3x48=144)</div>
	<div>30min.:30分毎の計測値を、現在時より3日間の範囲で表示(48x3=144)</div>
	<div>60min.:60分毎の計測値を、現在時より6日間の範囲で表示(24x6=144)</div>
    </div>
</body>
</html&gt
 
グラフ表示html(template.html)。
<html>
  <head>
    <title>Temperature Chart</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript">
      google.load("visualization", "1", {packages:["corechart"]});
      google.setOnLoadCallback(drawChart);
      function drawChart() {
        var data = google.visualization.arrayToDataTable([
          ['Date', '室内', 'カーテン', '内窓', '窓際'],
        {% for record in temp_list %}
          ['{{record.nitiji}}',  {{record.temp1}},  {{record.temp2}},  {{record.temp3}},  {{record.temp4}}],
        {% endfor %}
        ]);
        // グラフのオプションを設定
        var options = {
		title: '{{title}}',
			titleTextStyle: {
        	        color: '#444',    // タイトルの文字の色を設定
                	fontSize: 24      // タイトルの文字のサイズを設定
			},
		fontSize: 14,     // グラフ内の文字サイズを設定
		vAxis: {
                title: '温度',   // 縦軸のタイトルを設定
                maxValue: 35     // 縦軸の最大値を40に設定
                },
		colors: ['#FF0000', '#ffa500', '#008000', '#0000FF']
        };
        var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
        chart.draw(data, options);
      }
    </script>

    <style type="text/css">
    button {
        font-size: 24px;
    }
    </style>

  </head>
  <body>
 
  <div style="text-align:center;"> 
  <div id="chart_div" style="width: 100%; height: 500px;"></div>
  </br>
  <hr style="height: 6px; width: 900px; background-color: blue;">
  <button onclick="location.href='{{ url_for('index') }}'">戻る</button>
  </div>

  </body>
</html>
 
 
 
5. 表示結果
 
ボタン表示画面で、時間間隔と表示期間を選択。
ボタン表示画面で、時間間隔と表示期間を選択
 
10min.:10分毎の計測値を、現在時より24時間の範囲での表示結果。
10分毎の計測値を、現在時より24時間の範囲で表示
 
20min.:20分毎の計測値を、現在時より48時間の範囲での表示結果。
20分毎の計測値を、現在時より48時間の範囲で表示
 
30min.:30分毎の計測値を、現在時より3日間の範囲での表示結果。
30分毎の計測値を、現在時より3日間の範囲で表示
 
60min.:60分毎の計測値を、現在時より6日間の範囲での表示結果。
60分毎の計測値を、現在時より6日間の範囲で表示
 
 
「ChatGPT」と対話しながらプログラミングすると、プログラムの素人でもここまで作れる。
 
 
以上。
(2024.02.04)
 

 

スポンサー リンク

 

             

 

 

 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください