From 61fa840e5349bd153faf3cd10e395ecb8efc8c9e Mon Sep 17 00:00:00 2001 From: gitea Date: Fri, 28 Jun 2024 09:47:59 +0800 Subject: [PATCH] =?UTF-8?q?[t=5Fchange=5Fui:=20aio.py/brake.py/current.py]?= =?UTF-8?q?=20=E6=95=B4=E4=BD=93=E4=BF=AE=E6=94=B9=E4=BA=86=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E7=95=8C=E9=9D=A2=EF=BC=8C=E5=88=A0=E9=99=A4=E4=BA=86?= =?UTF-8?q?=E5=A4=A7=E9=83=A8=E5=88=86=E7=9A=84=E9=85=8D=E7=BD=AE=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E6=A1=86=EF=BC=8C=E6=94=B9=E7=94=A8=20configs.xlsx=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E6=9B=BF=E4=BB=A3=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E4=BA=86max/avg=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E4=B8=AD=E5=86=99=E5=85=A5=E7=BB=93=E6=9E=9C=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- aio/README.md | 28 ++-- aio/assets/configs.xlsx | Bin 0 -> 14863 bytes aio/assets/layout.xlsx | Bin 1224510 -> 1224091 bytes aio/assets/templates/heartbeat | 2 +- aio/code/aio.py | 135 +++++++----------- aio/code/data_process/brake.py | 235 +++++++++++++++++-------------- aio/code/data_process/current.py | 80 +++++++---- 7 files changed, 253 insertions(+), 227 deletions(-) create mode 100644 aio/assets/configs.xlsx diff --git a/aio/README.md b/aio/README.md index d3f6ac9..6b6f0fe 100644 --- a/aio/README.md +++ b/aio/README.md @@ -67,6 +67,7 @@ pyinstaller --noconfirm --onedir --windowed --add-data "C:/Users/Administrator/A ..../j1/reach33_自研_制动性能测试.xlsx ..../j1/reach66_自研_制动性能测试.xlsx ..../j1/reach100_自研_制动性能测试.xlsx + ..../j1/configs.xlsx 5. 文件的打开与关闭 a. 在执行程序之前,需要关闭所有相关 excle 文件 @@ -76,20 +77,27 @@ pyinstaller --noconfirm --onedir --windowed --add-data "C:/Users/Administrator/A 6. 数据准确性检查 执行完程序之后,可以在日志输出框中看到全部文件的处理过程,对于有问题的文件,会用特殊颜色进行标识,需要注意观察 -7. 其他 +7. configs.xlsx配置文件 +需要运行前需要手动修改该文件,并确保配置参数的正确性,测试时按照第 4 点中的位置存放 + +8. 其他 程序运行主要的耗时集中在打开,保存和关闭结果文件,第一次打开的时候会比较慢,另外还需要注意采集的数据长度和结果文件中预设的数据长度是否一致,若采集的数据长度大于预设的数据长度,则需要补齐数据 ``` #### 2) 电机电流 -1. 单独使用 max/avg 功能时,要求文件命名以 "jx_" 开头,例如 j1_2024_06_18_09_09_11.data,只允许有 .data 或者 .csv 文件,可同时处理所有轴的数据 +1. 单独使用 max/avg 功能时,要求文件命名以 "jx_" 开头,例如 j1_2024_06_18_09_09_11.data,只允许有 .data 或者 .csv 文件,以及configs.xlsx配置表,可同时处理所有轴的数据 2. cycle 功能支持处理单轴数据以及场景电机电流的数据,可以批量处理所有轴,但要确保遵守如下规则: - - 包含电机电流结果汇总文件(excel) - - 单轴文件:jx_xxxxx.data/csv - - 保持电流:jx_hold_xxxx.data/csv - - 场景文件:factory_53.8_2024_06_18_09_01_26.data(需手动拆分) - - 所有文件放在同一个文件夹即可 - - 界面输入rc参数时,需要输入所有轴的数据,即使只处理个别轴的数据 + +- 包含电机电流结果汇总文件(excel) +- 单轴文件:jx_xxxxx.data/csv +- 保持电流:jx_hold_xxxx.data/csv +- 场景文件:factory_53.8_2024_06_18_09_01_26.data(需手动拆分) +- 配置文件:configs.xlsx +- 所有文件放在同一个文件夹即可 +- 界面输入rc参数时,需要输入所有轴的数据,即使只处理个别轴的数据 + +3. configs.xlsx配置文件:需要运行前需要手动修改该文件,并确保配置参数的正确性 > 程序运行主要的耗时集中在打开,保存和关闭结果文件 > 需要注意采集的数据长度和结果文件中预设的数据长度是否一致,若采集的数据长度大于预设的数据长度,则需要补齐数据 @@ -307,3 +315,7 @@ v0.1.7.0(2024/06/26)-初步可用 9. [aio.py] 修改了版本 10. [current.py] max/avg功能结束之前会将结果数据追加写入源文件,avg算法更改为average+3×std 11. [wavelogger.py] 算法更改为 average+3×std + +v0.1.7.1(2024/06/29) +1. [t_change_ui: aio.py/brake.py/current.py] 整体修改了操作界面,删除了大部分的配置输入框,改用 configs.xlsx 配置文件替代,并优化了max/avg功能中写入结果数据的方式 +2. \ No newline at end of file diff --git a/aio/assets/configs.xlsx b/aio/assets/configs.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c8ae10d9848320456d57723ea35373093ff2e608 GIT binary patch literal 14863 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 diff --git a/aio/assets/templates/heartbeat b/aio/assets/templates/heartbeat index 56a6051..c227083 100644 --- a/aio/assets/templates/heartbeat +++ b/aio/assets/templates/heartbeat @@ -1 +1 @@ -1 \ No newline at end of file +0 \ No newline at end of file diff --git a/aio/code/aio.py b/aio/code/aio.py index 3795c23..7c00ba9 100644 --- a/aio/code/aio.py +++ b/aio/code/aio.py @@ -1,4 +1,3 @@ -import sys import tkinter from os.path import exists, dirname from os import getcwd @@ -26,22 +25,11 @@ btns_func = { } widgits_dp = { 'path': {'label': '', 'entry': '', 'row': 1, 'col': 2, 'text': '数据文件夹路径'}, - 'av': {'label': '', 'entry': '', 'row': 2, 'col': 2, 'text': '角速度'}, - 'rc': {'label': '', 'entry': '', 'row': 2, 'col': 4, 'text': '额定电流'}, - 'rpm': {'label': '', 'entry': '', 'row': 2, 'col': 6, 'text': '额定转速'}, - 'rr': {'label': '', 'entry': '', 'row': 2, 'col': 8, 'text': '减速比'}, - 'dur': {'label': '', 'entry': '', 'row': 2, 'col': 10, 'text': '周期时间'}, - 'axis': {'label': '', 'optionmenu': '', 'row': 3, 'col': 2, 'text': ''}, - 'vel': {'label': '', 'optionmenu': '', 'row': 3, 'col': 4, 'text': ''}, - 'trq': {'label': '', 'optionmenu': '', 'row': 3, 'col': 6, 'text': ''}, - 'trqh': {'label': '', 'optionmenu': '', 'row': 3, 'col': 8, 'text': ''}, - 'estop': {'label': '', 'optionmenu': '', 'row': 3, 'col': 10, 'text': ''}, - 'rc1': {'label': '', 'entry': '', 'row': 4, 'col': 2, 'text': '额定电流'}, - 'rc2': {'label': '', 'entry': '', 'row': 4, 'col': 4, 'text': '额定电流'}, - 'rc3': {'label': '', 'entry': '', 'row': 4, 'col': 6, 'text': '额定电流'}, - 'rc4': {'label': '', 'entry': '', 'row': 4, 'col': 8, 'text': '额定电流'}, - 'rc5': {'label': '', 'entry': '', 'row': 4, 'col': 10, 'text': '额定电流'}, - 'rc6': {'label': '', 'entry': '', 'row': 4, 'col': 12, 'text': '额定电流'}, + 'dur': {'label': '', 'entry': '', 'row': 2, 'col': 2, 'text': '周期时间'}, + 'vel': {'label': '', 'optionmenu': '', 'row': 2, 'col': 4, 'text': ''}, + 'trq': {'label': '', 'optionmenu': '', 'row': 2, 'col': 6, 'text': ''}, + 'trqh': {'label': '', 'optionmenu': '', 'row': 2, 'col': 8, 'text': ''}, + 'estop': {'label': '', 'optionmenu': '', 'row': 2, 'col': 10, 'text': ''}, } widgits_at = { 'path': {'label': '', 'entry': '', 'row': 2, 'col': 2, 'text': '数据文件夹路径'}, @@ -127,13 +115,13 @@ class App(customtkinter.CTk): widgits_dp[widgit]['entry'] = customtkinter.CTkEntry(self.tabview.tab('Data Process'), width=670, placeholder_text=widgits_dp[widgit]['text'], font=self.my_font) widgits_dp[widgit]['entry'].grid(row=widgits_dp[widgit]['row'], column=widgits_dp[widgit]['col']+1, columnspan=11, padx=(5, 10), pady=5, sticky='we') widgits_dp[widgit]['entry'].configure(state='disabled') - elif widgit in ['av', 'rc', 'rpm', 'rr', 'dur', 'rc1', 'rc2', 'rc3', 'rc4', 'rc5', 'rc6']: + elif widgit in ['dur']: widgits_dp[widgit]['label'] = customtkinter.CTkLabel(self.tabview.tab('Data Process'), text=f"{widgit.upper()}", font=self.my_font) widgits_dp[widgit]['label'].grid(row=widgits_dp[widgit]['row'], column=widgits_dp[widgit]['col'], sticky='e', pady=5) widgits_dp[widgit]['entry'] = customtkinter.CTkEntry(self.tabview.tab('Data Process'), width=self.w_param, placeholder_text=f"{widgits_dp[widgit]['text']}", font=self.my_font) widgits_dp[widgit]['entry'].grid(row=widgits_dp[widgit]['row'], column=widgits_dp[widgit]['col']+1, padx=(5, 10), pady=5, sticky='w') widgits_dp[widgit]['entry'].configure(state='disabled') - elif widgit in ['axis', 'vel', 'trq', 'trqh', 'estop']: + elif widgit in ['vel', 'trq', 'trqh', 'estop']: widgits_dp[widgit]['label'] = customtkinter.CTkLabel(self.tabview.tab('Data Process'), text=f"{widgit.upper()}", font=self.my_font) widgits_dp[widgit]['label'].grid(row=widgits_dp[widgit]['row'], column=widgits_dp[widgit]['col'], sticky='e', pady=5) widgits_dp[widgit]['optionmenu'] = customtkinter.CTkOptionMenu(self.tabview.tab('Data Process'), button_color='#708090', fg_color='#778899', values=["1", "2", "3", "4", "5", "6", "7"], width=self.w_param, font=self.my_font) @@ -182,7 +170,7 @@ class App(customtkinter.CTk): try: new_vers = urlopen(url_vers).read().decode('utf-8') if cur_vers.strip() != new_vers.strip(): - msg = f"""当前版本:{cur_vers}\n更新版本:{new_vers}\n\n请及时更新 http://10.2.23.150:10003/s/jRfM""" + msg = f"""当前版本:{cur_vers}\n更新版本:{new_vers}\n\n请及时前往钉盘更新~~~""" tkinter.messagebox.showwarning(title="版本更新", message=msg) except: tkinter.messagebox.showwarning(title="版本更新", message="连接服务器失败,无法确认当前是否是最新版本......") @@ -257,12 +245,12 @@ class App(customtkinter.CTk): tab_name = self.tabview.get() if tab_name == 'Data Process': for widgit in widgits_dp: - if widgit in ['path', 'av', 'rc', 'rpm', 'rr', 'dur', 'rc1', 'rc2', 'rc3', 'rc4', 'rc5', 'rc6']: + if widgit in ['path', 'dur']: widgits_dp[widgit]['label'].configure(text=f'{widgit.upper()}', text_color='black') widgits_dp[widgit]['entry'].delete(0, tkinter.END) widgits_dp[widgit]['entry'].configure(placeholder_text=widgits_dp[widgit]['text'], state='normal') widgits_dp[widgit]['entry'].configure(state='disabled') - elif widgit in ['axis', 'vel', 'trq', 'trqh', 'estop']: + elif widgit in ['vel', 'trq', 'trqh', 'estop']: widgits_dp[widgit]['label'].configure(text=f'{widgit.upper()}', text_color="black") widgits_dp[widgit]['optionmenu'].configure(state='normal') widgits_dp[widgit]['optionmenu'].set('1') @@ -290,10 +278,10 @@ class App(customtkinter.CTk): if tab_name == 'Data Process': if func_name == 'brake': for widgit in widgits_dp: - if widgit in ['path', 'av', 'rr']: + if widgit in ['path']: widgits_dp[widgit]['label'].configure(text_color='red') widgits_dp[widgit]['entry'].configure(state='normal') - elif widgit in ['axis', 'vel', 'trq', 'estop']: + elif widgit in ['vel', 'trq', 'estop']: widgits_dp[widgit]['label'].configure(text_color="red") widgits_dp[widgit]['optionmenu'].configure(state='normal') elif func_name == 'current': @@ -302,17 +290,9 @@ class App(customtkinter.CTk): self.menu_sub_dp.set("--select--") self.menu_sub_dp.configure(text_color='yellow') - for widgit in widgits_dp: - if widgit in ['path', 'rc', 'rc1', 'rc2', 'rc3', 'rc4', 'rc5', 'rc6']: - color = 'blue' if widgit == 'rc' else 'red' - widgits_dp[widgit]['label'].configure(text_color=color) - widgits_dp[widgit]['entry'].configure(state='normal') - elif widgit in ['trqh',]: - widgits_dp[widgit]['label'].configure(text_color="red") - widgits_dp[widgit]['optionmenu'].configure(state='normal') elif func_name == 'iso' or func_name == 'wavelogger': for widgit in widgits_dp: - if widgit in ['path',]: + if widgit in ['path']: widgits_dp[widgit]['label'].configure(text_color='red') widgits_dp[widgit]['entry'].configure(state='normal') else: @@ -322,30 +302,43 @@ class App(customtkinter.CTk): pass def func_sub_callback(self, func_name): - if func_name == "max": + if func_name == "max" or func_name == "avg": for widgit in widgits_dp: - if widgit in ['rpm', 'dur']: + if widgit in ['path']: + widgits_dp[widgit]['label'].configure(text_color='red') + widgits_dp[widgit]['entry'].delete(0, tkinter.END) + widgits_dp[widgit]['entry'].configure(placeholder_text=widgits_dp[widgit]['text'], state='normal') + widgits_dp[widgit]['entry'].configure(state='normal') + elif widgit in ['dur']: widgits_dp[widgit]['label'].configure(text_color='black') + widgits_dp[widgit]['entry'].delete(0, tkinter.END) + widgits_dp[widgit]['entry'].configure(placeholder_text=widgits_dp[widgit]['text'], state='normal') widgits_dp[widgit]['entry'].configure(state='disabled') - elif widgit in ['vel', 'trq']: - widgits_dp[widgit]['label'].configure(text_color='black') - widgits_dp[widgit]['optionmenu'].configure(state='disabled') - elif func_name == 'avg': - for widgit in widgits_dp: - if widgit in ['rpm', 'dur']: - widgits_dp[widgit]['label'].configure(text_color='black') - widgits_dp[widgit]['entry'].configure(state='disabled') - elif widgit in ['vel', 'trq']: + elif widgit in ['vel', 'trqh', 'estop']: widgits_dp[widgit]['label'].configure(text_color='black') + widgits_dp[widgit]['optionmenu'].set('1') widgits_dp[widgit]['optionmenu'].configure(state='disabled') + elif widgit in ['trq']: + widgits_dp[widgit]['label'].configure(text_color='red') + widgits_dp[widgit]['optionmenu'].set('1') + widgits_dp[widgit]['optionmenu'].configure(state='normal') elif func_name == 'cycle': for widgit in widgits_dp: - if widgit in ['rpm', 'dur']: - widgits_dp[widgit]['label'].configure(text_color='blue') + if widgit in ['path', 'dur']: + color = 'blue' if widgit == 'dur' else 'red' + widgits_dp[widgit]['label'].configure(text_color=color) + widgits_dp[widgit]['entry'].delete(0, tkinter.END) + widgits_dp[widgit]['entry'].configure(placeholder_text=widgits_dp[widgit]['text'], state='normal') widgits_dp[widgit]['entry'].configure(state='normal') - elif widgit in ['vel', 'trq']: - widgits_dp[widgit]['label'].configure(text_color="red") + elif widgit in ['vel', 'trq', 'trqh']: + color = 'blue' if widgit == 'trqh' else 'red' + widgits_dp[widgit]['label'].configure(text_color=color) + widgits_dp[widgit]['optionmenu'].set('1') widgits_dp[widgit]['optionmenu'].configure(state='normal') + elif widgit in ['estop']: + widgits_dp[widgit]['label'].configure(text_color="black") + widgits_dp[widgit]['optionmenu'].set('1') + widgits_dp[widgit]['optionmenu'].configure(state='disabled') def write2textbox(self, text, wait=0, exitcode=0, color='blue'): self.textbox.tag_add(color, 'insert', 'end') @@ -385,33 +378,20 @@ class App(customtkinter.CTk): func_name = self.menu_main_dp.get() if func_name == 'brake': path = widgits_dp['path']['entry'].get().strip() - av = widgits_dp['av']['entry'].get().strip('- ') - rr = widgits_dp['rr']['entry'].get().strip('- ') - axis = widgits_dp['axis']['optionmenu'].get() vel = widgits_dp['vel']['optionmenu'].get() trq = widgits_dp['trq']['optionmenu'].get() estop = widgits_dp['estop']['optionmenu'].get() c1 = exists(path) - c2 = self.is_float('required', av, rr) - c3 = True if len({vel, trq, estop}) == 3 else False - - if c1 and c2 and c3: - return 1, path, float(av), float(rr), int(axis), int(vel), int(trq), int(estop) + c2 = True if len({vel, trq, estop}) == 3 else False + if c1 and c2: + return 1, path, int(vel), int(trq), int(estop) else: return 0, 0 # ======================================================= elif func_name == 'current': path = widgits_dp['path']['entry'].get().strip() - rc = widgits_dp['rc']['entry'].get().strip('- ') - rpm = widgits_dp['rpm']['entry'].get().strip() dur = widgits_dp['dur']['entry'].get().strip() - rc1 = widgits_dp['rc1']['entry'].get().strip() - rc2 = widgits_dp['rc2']['entry'].get().strip() - rc3 = widgits_dp['rc3']['entry'].get().strip() - rc4 = widgits_dp['rc4']['entry'].get().strip() - rc5 = widgits_dp['rc5']['entry'].get().strip() - rc6 = widgits_dp['rc6']['entry'].get().strip() vel = widgits_dp['vel']['optionmenu'].get() trq = widgits_dp['trq']['optionmenu'].get() trqh = widgits_dp['trqh']['optionmenu'].get() @@ -419,23 +399,16 @@ class App(customtkinter.CTk): c1 = exists(path) c2 = sub in ['max', 'avg', 'cycle'] - c3 = self.is_float('optional', rc, rpm) - c4 = self.is_float('required', rc1, rc2, rc3, rc4, rc5, rc6) - - c5 = c6 = True + c3 = c4 = True if sub == 'cycle': - c5 = True if len({vel, trq}) == 2 else False - c6 = self.is_float('optional', dur) + c3 = True if len({vel, trq}) == 2 else False + c4 = self.is_float('optional', dur) + elif sub == 'max' or sub == 'avg': + pass - if c1 and c2 and c3 and c4 and c5 and c6: - rcs = [] - for x in [rc1, rc2, rc3, rc4, rc5, rc6]: - rcs.append(float(x)) - rc = 0 if rc == '' else rc - dur = 0 if sub != 'cycle' or dur == '' else dur - rpm = 0 if sub != 'cycle' or rpm == '' else rpm - rcs.append(float(rc)) - return 2, path, sub, rcs, int(vel), int(trq), int(trqh), float(dur), float(rpm) + if c1 and c2 and c3 and c4: + dur = 0 if dur == '' else dur + return 2, path, sub, float(dur), int(vel), int(trq), int(trqh) else: return 0, 0 # ======================================================= @@ -471,9 +444,9 @@ class App(customtkinter.CTk): flag, *args = self.check_param() func_dict = {1: brake.main, 2: current.main, 3: iso.main, 4: wavelogger.main} if flag == 1: - func_dict[flag](path=args[0], av=args[1], rr=args[2], axis=args[3], vel=args[4], trq=args[5], estop=args[6], w2t=self.write2textbox) + func_dict[flag](path=args[0], vel=args[1], trq=args[2], estop=args[3], w2t=self.write2textbox) elif flag == 2: - func_dict[flag](path=args[0], sub=args[1], rcs=args[2], vel=args[3], trq=args[4], trqh=args[5], dur=args[6], rpm=args[7], w2t=self.write2textbox) + func_dict[flag](path=args[0], sub=args[1], dur=args[2], vel=args[3], trq=args[4], trqh=args[5], w2t=self.write2textbox) elif flag == 3: func_dict[flag](path=args[0], w2t=self.write2textbox) elif flag == 4: diff --git a/aio/code/data_process/brake.py b/aio/code/data_process/brake.py index 664944d..233e0c6 100644 --- a/aio/code/data_process/brake.py +++ b/aio/code/data_process/brake.py @@ -27,78 +27,46 @@ class GetThreadResult(Thread): return None -def data_process(result_file, raw_data_dirs, av, rr, axis, vel, trq, w2t, estop): - # 功能:完成一个结果文件的数据处理 - # 参数:结果文件,数据目录,以及预读取的参数 - # 返回值:- - file_name = result_file.split('\\')[-1] - w2t(f"正在打开文件 {file_name} 需要 1min 左右", 1, 0, 'orange') +def traversal_files(path, w2t): + # 功能:以列表的形式分别返回指定路径下的文件和文件夹,不包含子目录 + # 参数:路径 + # 返回值:路径下的文件夹列表 路径下的文件列表 + if not exists(path): + msg = f'数据文件夹{path}不存在,请确认后重试......' + w2t(msg, 0, 1, 'red') + else: + dirs = [] + files = [] + for item in scandir(path): + if item.is_dir(): + dirs.append(item.path) + elif item.is_file(): + files.append(item.path) - global stop - stop = 0 - t_excel = GetThreadResult(load_workbook, args=(result_file, )) - t_wait = Thread(target=w2t_local, args=('.', 1, w2t)) - t_excel.start() - t_wait.start() - t_excel.join() - wb_result = t_excel.get_result() - stop = 1 - sleep(1.1) - w2t('') - - prefix = result_file.split('\\')[-1].split('_')[0] - for raw_data_dir in raw_data_dirs: - if raw_data_dir.split('\\')[-1].split('_')[0] == prefix: - now_doing_msg(raw_data_dir, 'start', w2t) - _, data_files = traversal_files(raw_data_dir, w2t) - # 数据文件串行处理模式--------------------------------- - # count = 1 - # for data_file in data_files: - # now_doing_msg(data_file, 'start', w2t) - # single_file_process(data_file, wb_result, count, av, rr, axis, vel, trq, w2t, estop) - # count += 1 - # now_doing_msg(data_file, 'done', w2t) - # --------------------------------------------------- - # 数据文件并行处理模式--------------------------------- - threads = [Thread(target=single_file_process, args=(data_files[0], wb_result, 1, av, rr, axis, vel, trq, w2t, estop)), - Thread(target=single_file_process, args=(data_files[1], wb_result, 2, av, rr, axis, vel, trq, w2t, estop)), - Thread(target=single_file_process, args=(data_files[2], wb_result, 3, av, rr, axis, vel, trq, w2t, estop))] - [t.start() for t in threads] - [t.join() for t in threads] - # --------------------------------------------------- - now_doing_msg(raw_data_dir, 'done', w2t) - - now_doing_msg(result_file, 'done', w2t) - w2t(f"正在保存文件 {file_name} 需要 1min 左右", 1, 0, 'orange') - stop = 0 - t_excel = Thread(target=wb_result.save, args=(result_file, )) - t_wait = Thread(target=w2t_local, args=('.', 1, w2t)) - t_excel.start() - t_wait.start() - t_excel.join() - stop = 1 - sleep(1.1) - w2t('\n') + return dirs, files -def check_files(raw_data_dirs, result_files, w2t): +def check_files(path, raw_data_dirs, result_files, w2t): # 功能:检查数据文件以及结果文件的合规性 # 参数:数据文件夹,结果文件 # 返回值:- - if len(result_files) != 3: - msg = "结果文件数目错误,结果文件有且只有三个,请确认!" + if len(result_files) != 4: for result_file in result_files: w2t(result_file) - w2t(msg, 0, 2, 'red') + w2t("需要有四个文件,包括三个结果文件,以及一个配置文件,请确认!", 0, 2, 'red') + + for result_file in result_files: + if result_file.endswith('configs.xlsx'): + result_files.remove(result_file) + break + else: + w2t("未找到配置文件,请确认!", 0, 8, 'red') prefix = [] for result_file in result_files: prefix.append(result_file.split('\\')[-1].split('_')[0]) if not sorted(prefix) == sorted(['reach33', 'reach66', 'reach100']): - wd = result_files[0].split('\\') - del wd[-1] - wd = '\\'.join(wd) - msg = f"""请关闭所有相关数据文件,并检查工作目录 {wd} 下,有且只允许有类似如下三个文件: + msg = f"""请关闭所有相关数据文件,并检查工作目录 {path} 下,有且只允许有类似如下三个文件: 1. reach33_XXX制动性能测试.xlsx 2. reach66_XXX制动性能测试.xlsx 3. reach100_XX制动性能测试.xlsx""" @@ -127,6 +95,20 @@ def check_files(raw_data_dirs, result_files, w2t): w2t("数据目录合规性检查结束,未发现问题......") +def get_configs(configfile, w2t): + axis = configfile.split('\\')[-2][-1] + if axis not in ['1', '2', '3']: + w2t("被处理的根文件夹命名必须是 [Jj][123] 的格式", 0, 9, 'red') + else: + axis = int(axis) + + _wb = load_workbook(configfile, read_only=True) + _ws = _wb['Target'] + rr = float(_ws.cell(row=2, column=axis+1).value) + av = float(_ws.cell(row=3, column=axis+1).value) + + return av, rr + def now_doing_msg(docs, flag, w2t): # 功能:输出正在处理的文件或目录 # 参数:文件或目录,start 或 done 标识 @@ -153,20 +135,6 @@ def w2t_local(msg, wait, w2t): break -def single_file_process(data_file, wb_result, count, av, rr, axis, vel, trq, w2t, estop): - # 功能:完成单个数据文件的处理 - # 参数:如上 - # 返回值:- - df = read_csv(data_file, sep='\t') - - conditions = sorted(data_file.split('\\')[-2].split('_')) # ['loadxx', 'reachxx', 'speedxx'] - result_sheet_name = find_result_sheet_name(conditions, count) - ws_result = wb_result[result_sheet_name] - - row_start, row_end = find_row_start(data_file, df, conditions, av, rr, axis, vel, w2t, estop) - copy_data_to_result(df, ws_result, row_start, row_end, vel, trq, estop) - - def copy_data_to_result(df, ws_result, row_start, row_end, vel, trq, estop): # 功能:将数据文件中有效数据拷贝至结果文件对应的 sheet # 参数:如上 @@ -193,26 +161,13 @@ def copy_data_to_result(df, ws_result, row_start, row_end, vel, trq, estop): ws_result.cell(row=_row, column=3).value = None -def find_result_sheet_name(conditions, count): - # 功能:获取结果文件准确的sheet页名称 - # 参数:臂展和速度的列表 - # 返回值:结果文件对应的sheet name - # 33%负载_33%速度_1 - ['loadxx', 'reachxx', 'speedxx'] - load = conditions[0].removeprefix('load') - speed = conditions[2].removeprefix('speed') - result_sheet_name = f"{load}%负载_{speed}%速度_{count}" - - return result_sheet_name - - -def find_row_start(data_file, df, conditions, av, rr, axis, vel, w2t, estop): +def find_row_start(data_file, df, conditions, av, rr, vel, estop, w2t): # 功能:查找数据文件中有效数据的行号,也即最后一个速度下降的点位 # 参数:如上 # 返回值:速度下降点位,最后的数据点位 ratio = float(conditions[2].removeprefix('speed'))/100 av_max = av * ratio row_max = df.index[-1] - # threshold = 30 if axis == 2 and conditions[0].removeprefix('load') == '100' else 10 threshold = 0.95 for _row in range(row_max, -1, -1): @@ -238,26 +193,89 @@ def find_row_start(data_file, df, conditions, av, rr, axis, vel, w2t, estop): return row_start, row_end -def traversal_files(path, w2t): - # 功能:以列表的形式分别返回指定路径下的文件和文件夹,不包含子目录 - # 参数:路径 - # 返回值:路径下的文件夹列表 路径下的文件列表 - if not exists(path): - msg = f'数据文件夹{path}不存在,请确认后重试......' - w2t(msg, 0, 1, 'red') - else: - dirs = [] - files = [] - for item in scandir(path): - if item.is_dir(): - dirs.append(item.path) - elif item.is_file(): - files.append(item.path) +def find_result_sheet_name(conditions, count): + # 功能:获取结果文件准确的sheet页名称 + # 参数:臂展和速度的列表 + # 返回值:结果文件对应的sheet name + # 33%负载_33%速度_1 - ['loadxx', 'reachxx', 'speedxx'] + load = conditions[0].removeprefix('load') + speed = conditions[2].removeprefix('speed') + result_sheet_name = f"{load}%负载_{speed}%速度_{count}" - return dirs, files + return result_sheet_name -def main(path, av, rr, axis, vel, trq, estop, w2t): +def single_file_process(data_file, wb_result, count, av, rr, vel, trq, estop, w2t): + # 功能:完成单个数据文件的处理 + # 参数:如上 + # 返回值:- + df = read_csv(data_file, sep='\t') + + conditions = sorted(data_file.split('\\')[-2].split('_')) # ['loadxx', 'reachxx', 'speedxx'] + result_sheet_name = find_result_sheet_name(conditions, count) + ws_result = wb_result[result_sheet_name] + + row_start, row_end = find_row_start(data_file, df, conditions, av, rr, vel, estop, w2t) + copy_data_to_result(df, ws_result, row_start, row_end, vel, trq, estop) + + +def data_process(result_file, raw_data_dirs, av, rr, vel, trq, estop, w2t): + # 功能:完成一个结果文件的数据处理 + # 参数:结果文件,数据目录,以及预读取的参数 + # 返回值:- + file_name = result_file.split('\\')[-1] + w2t(f"正在打开文件 {file_name} 需要 1min 左右", 1, 0, 'orange') + + global stop + stop = 0 + t_excel = GetThreadResult(load_workbook, args=(result_file, )) + t_wait = Thread(target=w2t_local, args=('.', 1, w2t)) + t_excel.start() + t_wait.start() + t_excel.join() + wb_result = t_excel.get_result() + stop = 1 + sleep(1.1) + w2t('') + + prefix = result_file.split('\\')[-1].split('_')[0] + for raw_data_dir in raw_data_dirs: + if raw_data_dir.split('\\')[-1].split('_')[0] == prefix: + now_doing_msg(raw_data_dir, 'start', w2t) + _, data_files = traversal_files(raw_data_dir, w2t) + # 数据文件串行处理模式--------------------------------- + # count = 1 + # for data_file in data_files: + # now_doing_msg(data_file, 'start', w2t) + # single_file_process(data_file, wb_result, count, av, rr, vel, trq, estop, w2t) + # count += 1 + # now_doing_msg(data_file, 'done', w2t) + # --------------------------------------------------- + # 数据文件并行处理模式--------------------------------- + threads = [ + Thread(target=single_file_process, args=(data_files[0], wb_result, 1, av, rr, vel, trq, estop, w2t)), + Thread(target=single_file_process, args=(data_files[1], wb_result, 2, av, rr, vel, trq, estop, w2t)), + Thread(target=single_file_process, args=(data_files[2], wb_result, 3, av, rr, vel, trq, estop, w2t)) + ] + [t.start() for t in threads] + [t.join() for t in threads] + # --------------------------------------------------- + now_doing_msg(raw_data_dir, 'done', w2t) + + now_doing_msg(result_file, 'done', w2t) + w2t(f"正在保存文件 {file_name} 需要 1min 左右", 1, 0, 'orange') + stop = 0 + t_excel = Thread(target=wb_result.save, args=(result_file, )) + t_wait = Thread(target=w2t_local, args=('.', 1, w2t)) + t_excel.start() + t_wait.start() + t_excel.join() + stop = 1 + sleep(1.1) + w2t('\n') + + +def main(path, vel, trq, estop, w2t): # 功能:执行处理所有数据文件 # 参数:initialization函数的返回值 # 返回值:- @@ -266,7 +284,8 @@ def main(path, av, rr, axis, vel, trq, estop, w2t): try: # threads = [] - check_files(raw_data_dirs, result_files, w2t) + check_files(path, raw_data_dirs, result_files, w2t) + av, rr = get_configs(path + '\\configs.xlsx', w2t) prefix = [] for raw_data_dir in raw_data_dirs: @@ -277,8 +296,8 @@ def main(path, av, rr, axis, vel, trq, estop, w2t): continue else: now_doing_msg(result_file, 'start', w2t) - data_process(result_file, raw_data_dirs, av, rr, axis, vel, trq, w2t, estop) - # threads.append(Thread(target=data_process, args=(result_file, raw_data_dirs, AV, RR, RC, AXIS))) + data_process(result_file, raw_data_dirs, av, rr, vel, trq, estop, w2t) + # threads.append(Thread(target=data_process, args=(result_file, raw_data_dirs, av, rr, vel, trq, estop, w2t))) # [t.start() for t in threads] # [t.join() for t in threads] except Exception as Err: @@ -295,4 +314,4 @@ def main(path, av, rr, axis, vel, trq, estop, w2t): if __name__ == "__main__": stop = 0 - main(path=argv[1], av=argv[2], rr=argv[3], axis=argv[4], vel=argv[5], trq=argv[6], estop=argv[7], w2t=argv[8]) + main(*argv[1:]) diff --git a/aio/code/data_process/current.py b/aio/code/data_process/current.py index 7c2b820..d99e263 100644 --- a/aio/code/data_process/current.py +++ b/aio/code/data_process/current.py @@ -63,19 +63,18 @@ def initialization(path, sub, w2t): for data_file in data_files: filename = data_file.split('\\')[-1] - if sub != 'cycle': + if data_file.endswith('configs.xlsx'): + count += 1 + elif sub == 'cycle' and data_file.endswith('.xlsx'): + count += 1 + else: if not (match('j[1-7].*\\.data', filename) or match('j[1-7].*\\.csv', filename)): + print(f"不合规 {data_file}") msg = f"所有文件必须以 jx_ 开头,以 .data/csv 结尾(x取值1-7),请检查后重新运行。" w2t(msg, 0, 6, 'red') - else: - if filename.endswith('.xlsx'): - count += 1 - elif not (match('j[1-7].*\\.data', filename) or match('j[1-7].*\\.csv', filename)): - msg = f"所有文件必须以 jx_ 开头,以 .data/csv 结尾(x取值1-7),请检查后重新运行。" - w2t(msg, 0, 7, 'red') - if sub == 'cycle' and count != 1: - w2t("未找到电机电流数据处理excel表格,确认后重新运行!", 0, 5, 'red') + if not ((sub == 'cycle' and count == 2) or (sub != 'cycle' and count == 1)): + w2t("使用max/avg功能时,需要有配置文件表格;使用cycle功能时,需要有电机电流数据处理和配置文件两个表格,确认后重新运行!", 0, 5, 'red') return data_files @@ -87,7 +86,10 @@ def current_max(data_files, rcs, trqh, w2t): df = read_csv(data_file, sep='\t') elif data_file.endswith('.csv'): df = read_csv(data_file, sep=',', encoding='gbk', header=8) + else: + continue + cols = len(df.columns) axis = int(data_file.split('\\')[-1].split('_')[0].removeprefix('j')) rca = rcs[axis-1] @@ -100,9 +102,9 @@ def current_max(data_files, rcs, trqh, w2t): w2t(f"{data_file}: {_:.4f}") with open(data_file, 'a+') as f_data: - csv_writer = writer(f_data) - csv_writer.writerow([''] * 4) - csv_writer.writerow([_]) + sep = '\t' if data_file.endswith('.data') else ',' + csv_writer = writer(f_data, delimiter=sep) + csv_writer.writerow([''] * (cols-1) + [_]) for axis, cur in current.items(): if not cur: @@ -123,7 +125,10 @@ def current_avg(data_files, rcs, trqh, w2t): df = read_csv(data_file, sep='\t') elif data_file.endswith('.csv'): df = read_csv(data_file, sep=',', encoding='gbk', header=8) + else: + continue + cols = len(df.columns) axis = int(data_file.split('\\')[-1].split('_')[0].removeprefix('j')) rca = rcs[axis-1] @@ -137,9 +142,9 @@ def current_avg(data_files, rcs, trqh, w2t): w2t(f"{data_file}: {_:.4f}") with open(data_file, 'a+') as f_data: - csv_writer = writer(f_data) - csv_writer.writerow([''] * 4) - csv_writer.writerow([_]) + sep = '\t' if data_file.endswith('.data') else ',' + csv_writer = writer(f_data, delimiter=sep) + csv_writer.writerow([''] * (cols-1) + [_]) for axis, cur in current.items(): if not cur: @@ -153,17 +158,17 @@ def current_avg(data_files, rcs, trqh, w2t): return current -def current_cycle(dur, data_files, rcs, vel, trq, trqh, rpm, w2t): +def current_cycle(dur, data_files, rcs, vel, trq, trqh, rpms, w2t): result = None hold = [] single = [] for data_file in data_files: filename = data_file.split('\\')[-1] - if data_file.endswith('.xlsx'): + if data_file.endswith('.xlsx') and not data_file.endswith('configs.xlsx'): result = data_file elif match('j[1-7]_hold_.*\\.data', filename) or match('j[1-7]_hold_.*\\.csv', filename): hold.append(data_file) - else: + elif match('j[1-7]_.*\\.data', filename) or match('j[1-7]_.*\\.csv', filename): single.append(data_file) w2t(f"正在打开文件 {result},需要 10s 左右", 1, 0, 'orange') @@ -189,9 +194,9 @@ def current_cycle(dur, data_files, rcs, vel, trq, trqh, rpm, w2t): pass if dur == 0: - p_single(wb, single, vel, trq, rpm, w2t) + p_single(wb, single, vel, trq, rpms, w2t) else: - p_scenario(wb, single, vel, trq, rpm, dur, w2t) + p_scenario(wb, single, vel, trq, rpms, dur, w2t) w2t(f"正在保存文件 {result},需要 10s 左右", 1, 0, 'orange') stop = 0 @@ -232,15 +237,15 @@ def find_point(data_file, pos, flag, df, _row_s, _row_e, w2t, exitcode, threshol w2t(f"[{pos}] {data_file}数据有误,需要检查,无法找到有效起始点或结束点...", 0, exitcode, 'red') -def p_single(wb, single, vel, trq, rpm, w2t): +def p_single(wb, single, vel, trq, rpms, w2t): # 1. 先找到第一个速度为零的点,数据从后往前找,一开始就是零的情况不予考虑 # 2. 记录第一个点的位置,继续向前查找第二个速度为零的点,同理,一开始为零的点不予考虑 # 3. 记录第二个点的位置,并将其中的数据拷贝至对应位置 for data_file in single: - rpm = 1 if rpm == 0 else rpm - scale = 1000 if data_file.endswith('.csv') else 1 axis = int(data_file.split('\\')[-1].split('_')[0].removeprefix('j')) shtname = f"J{axis}" + rpm = rpms[axis-1] if data_file.endswith('.csv') else 1 + scale = 1000 if data_file.endswith('.csv') else 1 ws = wb[shtname] addition = 1 set_option("display.precision", 2) @@ -313,13 +318,13 @@ def p_single(wb, single, vel, trq, rpm, w2t): cell.value = None -def p_scenario(wb, single, vel, trq, rpm, dur, w2t): +def p_scenario(wb, single, vel, trq, rpms, dur, w2t): for data_file in single: cycle = 0.001 - rpm = 1 if rpm == 0 else rpm - scale = 1000 if data_file.endswith('.csv') else 1 axis = int(data_file.split('\\')[-1].split('_')[0].removeprefix('j')) shtname = f"J{axis}" + rpm = rpms[axis-1] if data_file.endswith('.csv') else 1 + scale = 1000 if data_file.endswith('.csv') else 1 ws = wb[shtname] addition = 1 set_option("display.precision", 2) @@ -365,17 +370,34 @@ def p_scenario(wb, single, vel, trq, rpm, dur, w2t): cell.value = None -# ======================================= +def get_configs(configfile, w2t): + _wb = load_workbook(configfile, read_only=True) + _ws = _wb['Target'] + rcs = [] + rpms = [] + for i in range(2, 9): + try: + rpms.append(float(_ws.cell(row=4, column=i).value)) + except: + rpms.append(0.0) + + try: + rcs.append(float(_ws.cell(row=6, column=i).value)) + except: + rcs.append(0.0) + + return rpms, rcs -def main(path, sub, rcs, vel, trq, trqh, dur, rpm, w2t): +def main(path, sub, dur, vel, trq, trqh, w2t): data_files = initialization(path, sub, w2t) + rpms, rcs = get_configs(path + '\\configs.xlsx', w2t) if sub == 'max': current_max(data_files, rcs, trqh, w2t) elif sub == 'avg': current_avg(data_files, rcs, trqh, w2t) elif sub == 'cycle': - current_cycle(dur, data_files, rcs, vel, trq, trqh, rpm, w2t) + current_cycle(dur, data_files, rcs, vel, trq, trqh, rpms, w2t) else: pass