【実装】PC操作自動化システム

#概要
設計検討
https://qiita.com/asahi4549/items/b63affa193a2f422586a
をもとに実装してみた。

![ezgif.com-gif-maker (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700308/9e719392-3025-90c9-de79-acd797e4e73e.gif)

#各機能実装説明
GUI画面を示す。各機能について同実装したか説明する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700308/afb62999-4491-bd3f-3cce-efac4ae923d9.png)


"Read picture"クリック時の処理コードを示す。
フォルダを選択できるダイアログを表示し、そこで選択されたフォルダのpath情報を取得する。
path情報から選択されたフォルダ内の画像ファイルをリストデータとして保存しウィジェットに表示させている

```python:AK003_V1002.py

    def click_read_file(self):
        #ファイルオープンダイアログの表示
        #(file_path, selectedFilter) = QtWidgets.QFileDialog.getOpenFileName(self,filter="png jpeg (*.png *.jpeg)")
        #フォルダのみ選択する方法に変更
        #第2引数はダイアログのタイトル, 第3引数は初期表示したいpath
        dir_path_tmp = QtWidgets.QFileDialog.getExistingDirectory(self, 'Open Directory', "./picture")

        if(dir_path_tmp != ""):
            #path_absは絶対path
            path_abs = pathlib.Path(dir_path_tmp)
            #relative_toで相対パスに変換,引数は起点となるパス
            #path_abs.cwd()でカレントディレクトリのパスを取得
            self.dir_path_rel = path_abs.relative_to( path_abs.cwd() )      
            #pathからファイル名取得
            file_name  = os.listdir(self.dir_path_rel)
            #file_name = os.path.basename(file_path)
            print(file_name)
            #.pngファイル名をPicture listにセット
            #ファイル名をリストモデルに追加
            #self.list_model.setStringList([file_name])
            self.list_model.setStringList(file_name)
            #リストモデルをリストビューに追加
            #リストビューに追加することでウィジェットに反映される
            self.ui.listView_picture_list.setModel(self.list_model)
```



```python:AK003_V1002.py                 
    def click_execute(self):        
        #code_text解析処理
        #code textの文字列取得
        code = self.ui.plainTextEdit_code_text.toPlainText()

        
        #codeを引数にして文法チェック関数に渡す。check_syntaxの中で改行判定を行い、命令分を抽出する
        #selfも引数で渡さないと,uiの変数が扱えない
        syntax_result = Check_syntax(self,code)

        
        #textコードに問題がなければ実行用リストの実行
        if(syntax_result != FAIL):
            self.ui.plainTextEdit_result.appendPlainText("executed successfully")
            #デスクトップ画面を表示
            msg_ret = QMessageBox.question(None,"確認","自動化プログラムを実行しますがよろしいですか?", QMessageBox.Ok,QMessageBox.Cancel )
            if(msg_ret != QMessageBox.Cancel ):       
                #実行用リストを上から実行
                pg.sleep(2)
                QMessageBox.information(None, "情報","デスクトップ画面を表示してから2sec後にこの画面が表示されているはず")
                args_cnt = 0
                
                for func in self.list_exec_func:
                    exec_handler(self,func, self.list_exec_args[args_cnt])
                    time.sleep(0.5)
                    args_cnt = args_cnt + 1
                #実行用配列をクリア
                #2回目のコード実行時に前回の命令が残らないように
                self.list_exec_func.clear()
                self.list_exec_args.clear()
            else:
                self.ui.plainTextEdit_result.appendPlainText("program cancelled")
                return
        #文法チェックがngのときエラー処理
        else:
            self.ui.plainTextEdit_result.appendPlainText("execution failure")
            return
```



```python:AK003_V1002.py
    def click_save_code(self):
        #code_txtから文字列取得
        code = self.ui.plainTextEdit_code_text.toPlainText()
        
        #保存ファイル名取得
        (file_name_path, selectedFilter) = QtWidgets.QFileDialog.getSaveFileName(self, 'Open Directory', './code')
        if(file_name_path != ''):
            #pathからファイル名取得
            file_name  = os.path.basename(file_name_path)
            self.ui.plainTextEdit_result.appendPlainText(file_name+"を保存しました")
        #取得した文字列をほぞしたいファイルに書き出し
        #withを使うことで自動でcloseしてくれる
            with open('./code/'+file_name,"w" ) as save_code_file:
                save_code_file.write(code)
            
#コールバック関数
def exec_handler(self,func , *arg):
    func(self,*arg)
 

```python:AK003_V1002.py
#文法チェック関数
def Check_syntax(self,txt_code):
    
    #改行の文字列までの命令分を抽出
    list_code_tmp = []

    #特定の文字列の場所を取得して、そこまで削除を考えたが、改行でリストかする関数があったのでそちらを使う
    #改行ごとでリスト化
    list_code_tmp = txt_code.splitlines()
    print(list_code_tmp)
    
    #コードリストの中身を上から文法チェック
    for code in list_code_tmp:    #clickの文字を正規表現で抽出
        #note: matchは先頭文字からあっているか
        if(re.match('click',code)):
            #note 過去の文字を消さずに文字を追加
            #setPlainTextもあるがこれは過去の文字を無視する
            #self.ui.plainTextEdit_result.appendPlainText(code_tmp)
            #()があるか
            #note()も正規表現の一つなので\でエスケープしている
            #(から)で終わる文字の最後を抽出
            ob_recode_tmp = re.search('\(.*\)',code)
            print(ob_recode_tmp)
            #()が見つからない場合はエラー処理
            #note オブジェクトの一致判定は is をつかう
            if(ob_recode_tmp is not None):
                #group()でmatchした文字列を抽出
                code_tmp = ob_recode_tmp.group()
                #引数cod_argを抽出
                #note 0文字目と-1文字目以外を抽出
                code_arg = code_tmp[1:-1]
                print(code_arg)
                #clickの引数は画像idで与えられるので、idをlist_modelのインデックスのメンバにし、list_model内のデータを取得する
                #index指定して.dataでリストモデルに登録してるデータを取得できる
                #indexの引数はintなのでintに変換している
                code_arg = self.list_model.index(int(code_arg),0).data()
                
                #引数をselectpictureの中から探索
                #note stringListでlist_modelからリストを抽出,
                #if inより完全一致検索をかける。一文字でもあっている場合は判定したいときはmatch関数をつかう
                str_list = self.list_model.stringList()
                #str_listの中にcode_argがあるか判定
                if code_arg in str_list:
                    self.ui.plainTextEdit_result.appendPlainText("画像が見つかりました")
                    #clickを実行用リストに追加
                    
                    self.list_exec_func.append(pc_op.click)
                    self.list_exec_args.append(code_arg)
                else:
                    self.ui.plainTextEdit_result.appendPlainText("引数が正しくありません")
                    return FAIL
            else:
                self.ui.plainTextEdit_result.appendPlainText("()がありません")
                return FAIL            
        elif(re.match('dclick',code)):
            #note 過去の文字を消さずに文字を追加
            #setPlainTextもあるがこれは過去の文字を無視する
            #self.ui.plainTextEdit_result.appendPlainText(code_tmp)
            #()があるか
            #note()も正規表現の一つなので\でエスケープしている
            #(から)で終わる文字の最後を抽出
            ob_recode_tmp = re.search('\(.*\)',code)
            print(ob_recode_tmp)
            #()が見つからない場合はエラー処理
            #note オブジェクトの一致判定は is をつかう
            if(ob_recode_tmp is not None):
                #group()でmatchした文字列を抽出
                code_tmp = ob_recode_tmp.group()
                #引数cod_argを抽出
                #note 0文字目と-1文字目以外を抽出
                code_arg = code_tmp[1:-1]
                print(code_arg)
                #clickの引数は画像idで与えられるので、idをlist_modelのインデックスのメンバにし、list_model内のデータを取得する
                #index指定して.dataでリストモデルに登録してるデータを取得できる
                #indexの引数はintなのでintに変換している
                code_arg = self.list_model.index(int(code_arg),0).data()
                
                #引数をselectpictureの中から探索
                #note stringListでlist_modelからリストを抽出,
                #if inより完全一致検索をかける。一文字でもあっている場合は判定したいときはmatch関数をつかう
                str_list = self.list_model.stringList()
                #str_listの中にcode_argがあるか判定
                if code_arg in str_list:
                    self.ui.plainTextEdit_result.appendPlainText("画像が見つかりました")
                    #dclickとその引数を実行用リストに追加
                    self.list_exec_func.append(pc_op.dclick)
                    self.list_exec_args.append(code_arg)
                else:
                    self.ui.plainTextEdit_result.appendPlainText("引数が正しくありません")
                    return FAIL
            else:
                self.ui.plainTextEdit_result.appendPlainText("()がありません")
                return FAIL            
        elif(re.match('typekey',code)):
            #()があるか
            #note()も正規表現の一つなので\でエスケープしている
            #(から)で終わる文字の最後を抽出
            ob_recode_tmp = re.search('\(.*\)',code)
            #()が見つからない場合はエラー処理
            #note オブジェクトの一致判定は is をつかう
            if(ob_recode_tmp is not None):
                #group()でmatchした文字列を抽出
                code_tmp = ob_recode_tmp.group()
                #引数cod_argを抽出
                #note 0文字目と-1文字目以外を抽出
                code_arg = code_tmp[1:-1]
                print(code_arg)
                #typekey関数とその引数を実行リストに追加
                self.list_exec_func.append(pc_op.typekey)
                self.list_exec_args.append(code_arg)
            else:
                self.ui.plainTextEdit_result.appendPlainText("()がありません")
                return FAIL            
        elif(re.match('wait',code)):
            #()があるか
            #note()も正規表現の一つなので\でエスケープしている
            #(から)で終わる文字の最後を抽出
            ob_recode_tmp = re.search('\(.*\)',code)
            #()が見つからない場合はエラー処理
            #note オブジェクトの一致判定は is をつかう
            if(ob_recode_tmp is not None):
                #group()でmatchした文字列を抽出
                code_tmp = ob_recode_tmp.group()
                #引数cod_argを抽出
                #note 0文字目と-1文字目以外を抽出
                code_arg = code_tmp[1:-1]
                #typekey関数とその引数を実行リストに追加
                self.list_exec_func.append(pc_op.wait)
                self.list_exec_args.append(code_arg)
            else:
                self.ui.plainTextEdit_result.appendPlainText("()がありません")
                return FAIL                        
        elif(re.match('enter',code)):
            self.list_exec_func.append(pc_op.enter)
            code_arg = 0
            self.list_exec_args.append(code_arg)
        elif(re.match('desktop',code)):
            self.list_exec_func.append(pc_op.desktop)
            code_arg = 0
            self.list_exec_args.append(code_arg)            
        else:
            self.ui.plainTextEdit_result.appendPlainText("invalid function")
            return FAIL    

        
    return SUCCESS


```


```python:AK003_V1001func.py
# -*- coding: utf-8 -*-
"""
Created on Sun Dec 27 16:27:00 2020

@author: asahi
"""
import time
import pyautogui as pg
import threading


def click(self,args):
    print("clickが呼ばれました引数{}".format(args))
    #state_search = 'STATE_SEARCH_CHROME'
    #検索画像が見つかった場所の座標系が返る
    nsec = 0
    timeout = 5
    while True:
        #state_search = 'STATE_SEARCH_CHROME'
        #検索画像が見つかった場所の座標系が返る
        #button_position = get_locate_from_filename(args)
        #pg.hotkeyがすぐ動いてくれない?
        #note
        #.はpythonファイルがあるフォルダ(カレントディレクトリ)を表しpictureフォルダ内の画像を引数とする
        #+を使うことで文字列の中に変数を展開できる
        #print(str(self.dir_path_rel)+"\\"+args)
        #print("./picture/grope_0/"+args)
        #dir_path_relはwindowsパス(￥￥)表記なので+で連結できない
        #なのでstrで文字列に直す
        button_position = get_locate_from_filename("./"+str(self.dir_path_rel)+"/"+args)
        #button_position = get_locate_from_filename("./picture/grope_0/"+args)
        #button_position = get_locate_from_filename("C:/Users/asahi/Documents/30_業務/40_改善提案/FF_kitada/00_program/AK003_V1001/picture/grope_0/chrome.png")
        if(button_position != None):
            #pg.click(button_position)
            print("button_position = {}".format(button_position) )
            pg.click(button_position)
            break
        else:
            time.sleep(0.5)
            print("もう一回探します")            
            nsec += 1
            if( nsec > timeout):
                pg.alert(text = 'タイムアウト', button ='OK')
                break

def dclick(self,args):
    print("dclickが呼ばれました引数{}".format(args))
    #state_search = 'STATE_SEARCH_CHROME'
    #検索画像が見つかった場所の座標系が返る
    nsec = 0
    timeout = 5
    print(str(self.dir_path_rel))
    while True:
        button_position = get_locate_from_filename("./"+str(self.dir_path_rel)+"/"+args)
        if(button_position != None):
            print("button_position = {}".format(button_position) )
            pg.doubleClick(button_position)
            break
        else:
            time.sleep(0.5)
            print("もう一回探します")            
            nsec += 1
            if( nsec > timeout):
                pg.alert(text = 'タイムアウト', button ='OK')
                break
def typekey(self,args):
    print("typekeyが呼ばれました引数{}".format(args))
    
    pg.typewrite(args)

def enter(self,args):
    print("enterが呼ばれました引数{}".format(args))
    pg.press('enter',presses = 2, interval = 0.5)
    
def wait(self,args):
    print("waitが呼ばれました引数{}".format(args))
    #sleepの引数はintでないといけないのでキャストしている
    time.sleep(int(args))
def press_key(self,args):
    print("press_keyが呼ばれました")
    pass
def desktop(self,args):
    print("desktopが呼ばれました")              
    pg.hotkey('win','d')
    

#画像ファイルから座標を取得する処理
def get_locate_from_filename(filename):
    locate = None
    time.sleep(1)
    #グレイスケール処理で95%一致判定
    #検索画像の座標取得
    print(filename)
    try:
        locate = pg.locateCenterOnScreen(filename,grayscale = True,confidence=0.8)
        return locate
    except:
        print("画像ありません")
        return False

```