まずTkinterで、ボタンを押したらデータベースにデータを保存するプログラムを作りました。
Python
1def ボタンが押されたら(): 2 MySQLへデータを保存
のような要領です。
しかし、Raspberry Piではデータベースの書き込みに時間がかかるようで、ボタンを押したら数秒間ボタンが押し込みっぱなしの画面になります。
それが嫌だったので、別スレッドでデータを保存することにしました。
以下、ソースコードです。
Python
1from datetime import datetime 2import csv 3import pymysql 4import queue 5import threading 6import tkinter as tk 7 8tbl = "testtb" 9tmp = [] 10 11def cnct(): 12 global con, cur 13 # データベース接続 14 con = pymysql.connect( 15 host = "localhost", 16 user = "root", 17 password = "root", 18 db = "testdb", 19 charset = "utf8" 20 ) 21 cur = con.cursor() 22 23 24def discnct(): 25 global con, cur 26 # データベース切断 27 cur.close() 28 con.close() 29 30""" 31def read_and_csvout(): 32 global cur, tbl 33 cur.execute("SELECT * FROM " + tbl + ";") 34 results = cur.fetchall() 35 lst = [] 36 for i in range(len(results)): 37 tmp = results[i] 38 lst.append([ tmp[0], tmp[1], tmp[2] ]) 39 with open("/home/pi/デスクトップ/data.csv", "a", newline="") as f: 40 writer = csv.writer(f, lineterminator="\r\n") 41 for i in range(len(lst)): 42 writer.writerow(lst[i]) 43""" 44 45 46def pushed1(e): 47 global data 48 # データベース保存用データをキューに格納 49 for i in range(50): 50 data.put([str(i+1), "ButtonPushed1", ""]) 51 52 53def pushed2(e): 54 global data 55 # データベース保存用データをキューに格納 56 for i in range(50): 57 data.put([str(i+1), "ButtonPushed2", ""]) 58 59 60 61def loop(): 62 global loopflag, data, cur, con, tmp 63 # キュー監視ループ 64 while loopflag: 65 # キューにデータが存在したら、順番通りにデータベースへ保存する 66 while not data.empty(): 67 # データベースとの接続がタイムアウトしていたときのための再接続。 68 con.ping(reconnect=True) 69 # キューにある先頭データを取り出し、tmpに格納 70 tmp = data.get() 71 # tmpにあるデータをデータベースに保存 72 cur.execute("INSERT INTO " + tbl + " VALUES(%s,%s,%s);", (tmp[0], tmp[1], tmp[2])) 73 # コミット。データベースに変更を加えたらそれを反映させるために必要。 74 con.commit() 75 76 77def close(): 78 global loopflag, win, thread 79 # スレッド内のループフラグをFalseにする 80 loopflag = False 81 # スレッドが終わるまで待機 82 thread.join() 83 # データベースから切断 84 discnct() 85 # Tkinterアプリの終了 86 win.destroy() 87 88 89if __name__ == "__main__": 90 91 # データベースへ接続 92 cnct() 93 94 # スレッド内のループ用フラグ 95 loopflag = True 96 97 # データベースに保存するデータ格納用 98 data = queue.Queue() 99 100 # Tkinter UI 101 win = tk.Tk() 102 103 # ボタン 104 button1 = tk.Button(win, text="Button1") 105 button1.grid(row=0, column=0, sticky="nsew") 106 button2 = tk.Button(win, text="Button2") 107 button2.grid(row=0, column=1, sticky="nsew") 108 109 # データベース操作スレッド 110 # ※データベース操作はスレッドセーフではないため、1つのスレッドでのみ操作が可能 111 thread = threading.Thread(target=loop) 112 thread.start() 113 114 # ボタンバインド 115 button1.bind("<ButtonRelease>", pushed1) 116 button2.bind("<ButtonRelease>", pushed2) 117 118 # UIクローズ検知 119 win.protocol("WM_DELETE_WINDOW", close) 120 121 # イベントループ 122 win.mainloop() 123 124 125""" 126MariaDB [testdb]> DESC testtb; 127+-------+------+------+-----+---------+-------+ 128| Field | Type | Null | Key | Default | Extra | 129+-------+------+------+-----+---------+-------+ 130| data1 | text | YES | | NULL | | 131| data2 | text | YES | | NULL | | 132| data3 | text | YES | | NULL | | 133+-------+------+------+-----+---------+-------+ 1343 rows in set (0.00 sec) 135""" 136
ボタンを押したら保存するところまではできたのですが、
ここからさらに機能の実装をしたいと考えています。
内容として、Button2を押したらデータをデータベースに保存してからCSV出力するということをしたいです。
例えば、Button1押下→Button2押下とした場合、上のソースコードだと
CSV
11,ButtonPushed1 22,ButtonPushed1 33,ButtonPushed1 4(省略) 549,ButtonPushed1 650,ButtonPushed1 71,ButtonPushed2 82,ButtonPushed2 9(省略) 1049,ButtonPushed2 1150,ButtonPushed2
というCSVを出力したいです。
このときにButton2を押したときの処理が、メインスレッドだとバッファにキューに保存しているだけなのでMySQL操作しているスレッドの動作まで検知できません。
(グローバル変数で適当なフラグを立てることを考えたのですが、Button1のデータ書き込みが終わったのか、Button2のデータ書き込みが終わったのか、そもそも書き込んでいないのかまではloop()内で区別できないような気がします。変数だけじゃなくコールバック関数などを駆使すればできる?)
どのようにすれば 50,ButtonPushed2 をデータベースに保存した後でCSV出力をすることが可能でしょうか。
(ただし、出力データ 50,ButtonPushed2 はあくまでサンプルデータにすぎないので、実際に保存するデータはどうなるか分かりません。そのためスレッドのループ内に下記のような指定をするのはなしでお願いします。)
def loop(): global loopflag, data, cur, con, tmp # キュー監視ループ while loopflag: # キューにデータが存在したら、順番通りにデータベースへ保存する while not data.empty(): # データベースとの接続がタイムアウトしていたときのための再接続。 con.ping(reconnect=True) # キューにある先頭データを取り出し、tmpに格納 tmp = data.get() # tmpにあるデータをデータベースに保存 cur.execute("INSERT INTO " + tbl + " VALUES(%s,%s,%s);", (tmp[0], tmp[1], tmp[2])) # データの内容を指定して、CSV出力(これは解決策としては×) if tmp == ["50", "ButtonPushed2", ""]: sqldata = データベース読み込み() CSV出力(sqldata) データベースクリア() # コミット。データベースに変更を加えたらそれを反映させるために必要。 con.commit()
質問後追記 変更(2020/10/08 14:39)
Python:
1from datetime import datetime 2import csv 3import pymysql 4import queue 5import threading 6import time 7import tkinter as tk 8 9tbl = "testtb" 10tmp = [] 11 12def cnct(): 13 # データベース接続 14 con = pymysql.connect( 15 host = "localhost", 16 user = "root", 17 password = "root", 18 db = "testdb", 19 charset = "utf8" 20 ) 21 cur = con.cursor() 22 23 return con, cur 24 25 26def discnct(con, cur): 27 # データベース切断 28 cur.close() 29 con.close() 30 31 32def read_and_csvout(cur): 33 global tbl 34 cur.execute("SELECT * FROM " + tbl + ";") 35 results = cur.fetchall() 36 lst = [] 37 for i in range(len(results)): 38 tmp = results[i] 39 lst.append([ tmp[0], tmp[1], tmp[2] ]) 40 with open("/home/pi/デスクトップ/data.csv", "a", newline="") as f: 41 writer = csv.writer(f, lineterminator="\r\n") 42 for i in range(len(lst)): 43 writer.writerow(lst[i]) 44 45 46def clear(cur): 47 global tbl 48 cur.execute("DELETE FROM " + tbl + ";") 49 50 51def pushed1(e): 52 global data 53 # データベース保存用データをキューに格納 54 for i in range(50): 55 data.put(("DB-INSERT", [str(i+1), "ButtonPushed1", ""])) 56 data.put(("DB-COMMIT", None)) 57 58 59def pushed2(e): 60 global data 61 # データベース保存用データをキューに格納 62 for i in range(50): 63 data.put(("DB-INSERT", [str(i+1), "ButtonPushed2", ""])) 64 data.put(("DB-COMMIT", None)) 65 data.put(("CSV-OUTPUT", None)) 66 67 68 69def loop(data): 70 con, cur = cnct() 71 # キュー監視ループ 72 while True: 73 # キューにある先頭データを取り出す 74 msg, args = data.get() 75 print(msg, args) 76 if msg == "DB-INSERT": 77 # データベースとの接続がタイムアウトしていたときのための再接続。 78 con.ping(reconnect=True) 79 # tmpにあるデータをデータベースに保存 80 cur.execute("INSERT INTO " + tbl + " VALUES(%s,%s,%s);", (args[0], args[1], args[2])) 81 elif msg == "CSV-OUTPUT": 82 # データベースとの接続がタイムアウトしていたときのための再接続。 83 con.ping(reconnect=True) 84 read_and_csvout(cur) 85 clear(cur) 86 con.commit() 87 elif msg == "DB-COMMIT": 88 con.commit() 89 elif msg == "DB-DISCNCT": 90 break 91 else: 92 break 93 discnct(con, cur) 94 95 96def close(): 97 global data, win, thread 98 data.put(("DB-DISCNCT", None)) 99 thread.join() 100 # Tkinterアプリの終了 101 win.destroy() 102 103 104if __name__ == "__main__": 105 106 # データベースに保存するデータ格納用 107 data = queue.Queue() 108 109 # Tkinter UI 110 win = tk.Tk() 111 112 # ボタン 113 button1 = tk.Button(win, text="Button1") 114 button1.grid(row=0, column=0, sticky="nsew") 115 button2 = tk.Button(win, text="Button2") 116 button2.grid(row=0, column=1, sticky="nsew") 117 118 # データベース操作スレッド 119 # ※データベース操作はスレッドセーフではないため、1つのスレッドでのみ操作が可能 120 thread = threading.Thread(target=loop, args=(data, )) 121 thread.start() 122 123 # ボタンバインド 124 button1.bind("<ButtonRelease>", pushed1) 125 button2.bind("<ButtonRelease>", pushed2) 126 127 # UIクローズ検知 128 win.protocol("WM_DELETE_WINDOW", close) 129 130 # イベントループ 131 win.mainloop() 132 133 134""" 135MariaDB [testdb]> DESC testtb; 136+-------+------+------+-----+---------+-------+ 137| Field | Type | Null | Key | Default | Extra | 138+-------+------+------+-----+---------+-------+ 139| data1 | text | YES | | NULL | | 140| data2 | text | YES | | NULL | | 141| data3 | text | YES | | NULL | | 142+-------+------+------+-----+---------+-------+ 1433 rows in set (0.00 sec) 144""" 145
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/10/08 01:42
2020/10/08 02:00 編集
2020/10/08 02:06
2020/10/08 02:13
2020/10/08 03:03
2020/10/08 03:07
2020/10/08 03:14 編集
2020/10/08 03:18
2020/10/08 04:18 編集
2020/10/08 04:53
2020/10/08 05:42
2020/10/08 12:56
2020/10/08 22:44