研究室のオンラインミーティング自動開催ツールを弄る回です。
完成当時の実装ですと、RPAツールが例外を飛ばして落ちた瞬間にGUIも落ちるので、これらをスレッドとして分割してRPAツールがGUIに影響を与えないようにします。
1.修正後のコード
import PySimpleGUI as sg | |
import datetime as dt | |
import getschedule as gs | |
import operate_zoom as oz | |
import time | |
import threading | |
sg.theme('DarkBrown1') | |
layout = [ | |
[sg.Text('Next', size = (5,1), justification = 'left'), sg.Text(size = (20,1), key='-START-'),sg.Text('~',size = (6,1)), sg.Text(size=(20,1), key='-END-')], | |
[sg.Text('Now',size = (5,1),justification = 'left'),sg.Text(size=(20,1), key = '-NOW-')], | |
[sg.Text('Log', size =(5,1), justification = 'left')], | |
[sg.Output(size=(100,10), key='-LOG-')], | |
[sg.Button('Start/Stop', focus=True), sg.Quit()] | |
] | |
window = sg.Window('ミーティング開催', layout) | |
count = -1 #初回ループ時に予定取得させるため | |
def start_zoom(now_date): | |
oz.Start_Zoom() | |
print('{} Start meeting'.format(now_date)) | |
#タイミング調整 | |
time.sleep(5) | |
#画面共有開始 | |
oz.screen_sharing() | |
def end_zoom(now_date): | |
global count | |
zoom_flag = True | |
oz.Stop_Zoom() | |
print('{} Quit meeting'.format(now_date)) | |
#ミーティング終了後、即座に予定を再取得する | |
count = -1 | |
def GUI(): | |
global count | |
RPA_running = False | |
next_start_date, next_end_date = '0000-00-00 00:00', '0000-00-00 00:00' | |
interval = 0 #ミーティング開始時、終了時から1分間カウントする変数 | |
zoom_flag = False #何度も起動するのを防ぐため。Trueの場合1分経過でFalseに戻す。 | |
while True: | |
event, values = window.read(timeout=1000) #ループ間隔は1秒 | |
#現在時刻を取得 | |
now = dt.datetime.now() | |
#時刻表示のために整形 | |
now_time = now.strftime('%Y-%m-%d %H:%M:%S') | |
#比較のためにstr型に変換 | |
now_date = now.strftime('%Y-%m-%d %H:%M') | |
#現在時刻の表示 | |
window['-NOW-'].update(now_time) | |
#ボタン操作 | |
if event in ('Quit'): | |
break | |
elif event == 'Start/Stop': | |
RPA_running = not RPA_running | |
if RPA_running: | |
print('{} Start'.format(now_date)) | |
else: | |
print('{} Stop'.format(now_date)) | |
#再開時に予定を取得させるため | |
count = -1 | |
if RPA_running: | |
#30分間隔で予定取得 | |
if count == -1 or count == 1800: | |
#カウンタを戻す | |
count = 0 | |
#予定を5つ取得し、直近の予定の開始時刻と終了時刻を格納 | |
schedule = gs.get_events() | |
start_date = schedule[0] | |
end_date = schedule[1] | |
#予定を整形 | |
start_date = start_date[0:10] + ' ' + start_date[11:16] | |
end_date = end_date[0:10] + ' ' + end_date[11:16] | |
#予定を取得したらログに記述 | |
print('{} Get next schedule'.format(now_date)) | |
#次の予定が変更されたらログに記述 | |
if start_date != next_start_date: | |
next_start_date = start_date | |
next_end_date = end_date | |
print('{} Update next schedule'.format(now_date)) | |
#GUI表示 | |
window['-START-'].update(start_date) | |
window['-END-'].update(end_date) | |
#ミーティング開始時、終了時にカウントを始める | |
if zoom_flag == True: | |
interval += 1 | |
#60秒経過したらフラッグを反転させ、変数を初期値に戻す | |
if interval == 60: | |
zoom_flag = not zoom_flag | |
interval = 0 | |
#開始時刻かつ直前にミーティングを起動していないならミーティングを開始する | |
if start_date == now_date and zoom_flag == False: | |
zoom_flag = True | |
zoom_thread = threading.Thread(target=start_zoom, daemon=True, args=(now_date,)) | |
zoom_thread.start() | |
#終了時刻にかつ直前にミーティングを終了していないならミーティングを終了する | |
if end_date == now_date and zoom_flag == False: | |
zoom_flag = True | |
zoom_thread = threading.Thread(target = end_zoom, daemon = True, args = (now_date,)) | |
zoom_thread.start() | |
#カウンタを増やす | |
count += 1 | |
else: | |
continue | |
window.close() | |
if __name__ == '__main__': | |
GUI_thread = threading.Thread(target=GUI) | |
GUI_thread.start() |
2.Pythonの並列処理
threadingというモジュールが用意されています。これを用いると容易に実装できます。
他にもスレッドプールやプロセスルールなどもあります。
今回は、GUIを走らせるスレッドとRPAツールを走らせるスレッドを分けるだけのため、スレッドプールのように同時に動かす最大数を制限する必要はなく、プロセスとして分割してマルチコアで動かす必要もないため、threadingで並列処理を行いました。
3.並列処理の前に
もともとRPAツール部分はGUI内のwhile文内に書かれていました。しかし、threadingで並列処理する場合は、実行する関数とともにTreadクラスをインスタンス化する必要があります。要するに、今回の変更ではRPAツールを関数化する必要がありました。
4.この変数ってグローバル変数の必要ある?
RPAツールの関数化とともに、グローバル環境で宣言している変数についても見直しを行いました。グローバル環境にすべて変数を宣言するのはお行儀が良くないので、できる限りその変数を使う関数内に移動させました。
5.Pythonのグローバル変数を関数内で使う時
グローバル変数とローカル変数に同じ名前をつけることができます。また、関数内で宣言されるとローカル変数と扱われます。関数内でグローバル関数を使う場合は、変数名の前にglobalをつけます。
6.startするのを忘れない
インスタンス化した後、startするのを忘れると意味がないので忘れないようにしましょう。
7.おわりに
調べてすぐにマルチスレッド化できてびっくりしました。でもマルチスレッド化ならC++も比較的簡単だった覚えがあります。
実装予定の機能ができたので、次はログをファイルに出力するようにしたいです。ロギング機能ってのがあるらしい。
8.参考文献
Python♪関数で変なエラーが出た:local variable ‘x’ referenced before assignment