From de6d1d47c84e4c68a07eb77d63f41fc688df5a22 Mon Sep 17 00:00:00 2001 From: gitea Date: Thu, 23 May 2024 11:25:45 +0800 Subject: [PATCH] =?UTF-8?q?[midify]=20v0.0.4(2024/05/22)=201.=20=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E6=A0=87=E5=AE=9A=E4=BA=86get=5Fthreshold=5Fstep?= =?UTF-8?q?=E5=87=BD=E6=95=B0=EF=BC=8C=E8=AE=A9=E5=A4=84=E7=90=86=E6=9B=B4?= =?UTF-8?q?=E5=8A=A0=E5=87=86=E7=A1=AE=202.=20=E6=96=B0=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E4=BA=86now=5Fdoing=5Fmsg=E5=87=BD=E6=95=B0=EF=BC=8C=E5=AE=9E?= =?UTF-8?q?=E6=97=B6=E8=BE=93=E5=87=BA=E5=A4=84=E7=90=86=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=203.=20=E4=BF=AE=E6=94=B9=E4=BA=86find=5Frow=5Fstart=E5=92=8Cf?= =?UTF-8?q?ind=5Frow=5Fstart=5Fdp=E5=87=BD=E6=95=B0=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E7=9A=84=E9=83=A8=E5=88=86=E7=9B=B8=E5=90=8C=EF=BC=8C?= =?UTF-8?q?=E5=A4=84=E7=90=86=E6=95=B0=E6=8D=AE=E7=9A=84=E6=97=B6=E5=80=99?= =?UTF-8?q?=EF=BC=8C=E5=85=88=E5=88=A4=E6=96=AD=E6=98=AF=E5=90=A6=E6=98=AF?= =?UTF-8?q?=E7=A9=BA=E5=80=BC=EF=BC=8C=E6=88=96=E8=80=85=E6=98=AF0?= =?UTF-8?q?=EF=BC=8C=E6=AD=A4=E6=97=B6=E5=8F=AF=E4=BB=A5=E5=8A=A0=E5=BF=AB?= =?UTF-8?q?=E6=AD=A5=E8=BF=9B=204.=20=E4=BF=AE=E6=94=B9=E4=BA=86just=5Fope?= =?UTF-8?q?n=E5=87=BD=E6=95=B0=EF=BC=8C=E4=B8=8D=E5=9C=A8=E5=81=9A?= =?UTF-8?q?=E9=87=8D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rokae/brake/brake.py | 160 +++++++++++++++++------------- rokae/brake/configs.xlsx | Bin 0 -> 10692 bytes rokae/brake/configuration.xlsx | Bin 9577 -> 0 bytes rokae/brake/file_version_info.txt | 8 +- 4 files changed, 93 insertions(+), 75 deletions(-) create mode 100644 rokae/brake/configs.xlsx delete mode 100644 rokae/brake/configuration.xlsx diff --git a/rokae/brake/brake.py b/rokae/brake/brake.py index 290abf5..ee44fdc 100644 --- a/rokae/brake/brake.py +++ b/rokae/brake/brake.py @@ -1,45 +1,39 @@ # coding: utf-8 -from os import scandir, remove -from sys import exit -from openpyxl import load_workbook +import os +import sys +import openpyxl from win32com.client import DispatchEx -from time import time, strftime, localtime, sleep +import time from threading import Thread import pythoncom -from pandas import read_csv +import pandas def just_open(filename): - for i in range(3): - try: - pythoncom.CoInitialize() - xlapp = DispatchEx("Excel.Application") - xlapp.Visible = False - xlbook = xlapp.Workbooks.Open(filename) - xlapp.DisplayAlerts = False - xlbook.SaveAs(filename) - xlbook.Close() - xlapp.Quit() - except Exception as Err: - if xlbook is None: - xlbook.SaveAs(filename) - xlbook.close() - if xlapp is not None: - xlapp.Quit() - print(f"使用win32com打开【{filename}】文件,第 {i} 次操作失败,静默三秒钟,等待重新执行......") - sleep(3) + pythoncom.CoInitialize() + xlapp = DispatchEx("Excel.Application") + xlapp.Visible = False + xlbook = xlapp.Workbooks.Open(filename) + xlapp.DisplayAlerts = 0 + xlbook.SaveAs(filename) + xlbook.Close() + xlapp.Quit() def traversal_files(path): - dirs = [] - files = [] - for item in scandir(path): - if item.is_dir(): - dirs.append(item.path) - elif item.is_file(): - files.append(item.path) + if not os.path.exists(path): + msg = f'数据文件夹{path}不存在,请确认后重试......' + warn_pause_exit(msg, 1, 11) + else: + dirs = [] + files = [] + for item in os.scandir(path): + if item.is_dir(): + dirs.append(item.path) + elif item.is_file(): + files.append(item.path) - return dirs, files + return dirs, files def get_threshold_step(excel_file, AXIS): @@ -65,6 +59,11 @@ def find_row_start(excel_file, ws_data, conditions, AV, RR, AXIS): threshold, step = get_threshold_step(excel_file, AXIS) while row_start > 0: + speed = ws_data[f"A{row_start}"].value + if speed is None or int(speed) < 1: + row_start -= step + continue + row_end = row_start - step if row_end < 2: msg = f"可能是{excel_file.replace('xlsx', 'data')}, 这个文件数据采集有问题,也有可能是程序步长设定问题......" \ @@ -78,7 +77,7 @@ def find_row_start(excel_file, ws_data, conditions, AV, RR, AXIS): else: row_start -= step else: - remove(excel_file) + os.remove(excel_file) msg = f"可能是{excel_file.replace('xlsx', 'data')},这个文件数据采集有问题,比如采集的时机不对,请检查......" warn_pause_exit(msg, 1, 9) @@ -136,8 +135,9 @@ def copy_data_to_excel_file(wb_data, ws_result, row_max, row_start, excel_file, ws_dp.cell(row=6, column=7).value = RR wb_data.save(excel_file) + wb_data.close() just_open(excel_file) # 为了能读取到公式计算的数值,必须要用 win32com 打开关闭一次 - wb_data = load_workbook(excel_file, data_only=True) + wb_data = openpyxl.load_workbook(excel_file, data_only=True) ws_dp = wb_data['dp'] return wb_data, ws_dp @@ -149,9 +149,10 @@ def find_row_start_dp(data_file, ws_dp, row_max, row_start, conditions, AV): row_max_dp = row_max - row_start + 1 + 1 # title row row_start_dp = row_max_dp - 5 while row_start_dp > 6: - # 处理异常数据:当从数据文件中拷贝的有效数据超过5000时,会触发该代码块 - if ws_dp.cell(row=row_start_dp, column=4).value is None: - row_start_dp -= 100 + # 处理异常数据:当从数据文件中拷贝的有效数据超过5000时,会触发下面代码块 + angular = ws_dp.cell(row=row_start_dp, column=4).value + if angular is None or str(angular) == '0': + row_start_dp -= 50 continue _a = float(ws_dp.cell(row=row_start_dp, column=4).value) _b = float(ws_dp.cell(row=row_start_dp - 1, column=4).value) @@ -172,9 +173,9 @@ def find_row_start_dp(data_file, ws_dp, row_max, row_start, conditions, AV): def single_file_process(data_file, wb_result, count, AV, RR, RC, AXIS): - excel_file = data_file.replace('data', 'xlsx') + excel_file = data_file.replace('.data', '.xlsx') sheet_name = data_file.split('\\')[-1].removesuffix('.data') - df = read_csv(data_file, sep='\t') + df = pandas.read_csv(data_file, sep='\t') df.to_excel(excel_file, sheet_name=sheet_name, index=False) conditions = sorted(data_file.split('\\')[-2].split('_')[1:]) @@ -182,7 +183,7 @@ def single_file_process(data_file, wb_result, count, AV, RR, RC, AXIS): result_sheet_name = find_result_sheet_name(conditions, count) ws_result = wb_result[result_sheet_name] - wb_data = load_workbook(excel_file) + wb_data = openpyxl.load_workbook(excel_file) ws_data = wb_data[sheet_name] row_max, row_start = find_row_start(excel_file, ws_data, conditions, AV, RR, AXIS) @@ -196,25 +197,46 @@ def single_file_process(data_file, wb_result, count, AV, RR, RC, AXIS): wb_data.close() +def now_doing_msg(docs, flag): + now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + file_type = 'file' if os.path.isfile(docs) else 'dir' + if flag == 'start' and file_type == 'dir': + print(f"[{now}] 正在处理目录【{docs}】中的数据......") + elif flag == 'start' and file_type == 'file': + print(f"[{now}] 正在处理文件【{docs}】中的数据......") + elif flag == 'done' and file_type == 'dir': + print(f"[{now}] 目录【{docs}】数据文件已处理完毕......") + elif flag == 'done' and file_type == 'file': + print(f"[{now}] 文件【{docs}】数据文件已处理完毕......") + + def data_process(result_file, raw_data_dirs, AV, RR, RC, AXIS): prefix = result_file.split('\\')[-1].split('_')[0] - wb_result = load_workbook(result_file) # 打开和关闭结果文件夹十分耗时间 + wb_result = openpyxl.load_workbook(result_file) # 打开和关闭结果文件夹十分耗时间 for raw_data_dir in raw_data_dirs: if raw_data_dir.split('\\')[-1].split('_')[0] == prefix: - now = strftime('%Y-%m-%d %H:%M:%S', localtime(time())) - print(f"[{now}] 正在处理目录【{raw_data_dir}】中的数据......") + now_doing_msg(raw_data_dir, 'start') _, data_files = traversal_files(raw_data_dir) + # 数据文件串行处理模式--------------------------------- + # count = 1 + # for data_file in data_files: + # now_doing_msg(data_file, 'start') + # single_file_process(data_file, wb_result, count, AV, RR, RC, AXIS) + # count += 1 + # now_doing_msg(data_file, 'done') + # --------------------------------------------------- + # 数据文件并行处理模式--------------------------------- threads = [Thread(target=single_file_process, args=(data_files[0], wb_result, 1, AV, RR, RC, AXIS)), Thread(target=single_file_process, args=(data_files[1], wb_result, 2, AV, RR, RC, AXIS)), Thread(target=single_file_process, args=(data_files[2], wb_result, 3, AV, RR, RC, AXIS))] [t.start() for t in threads] [t.join() for t in threads] - now = strftime('%Y-%m-%d %H:%M:%S', localtime(time())) - print(f"[{now}] 目录【{raw_data_dir}】中的数据已处理完成......") + now_doing_msg(raw_data_dir, 'done') + # --------------------------------------------------- - now = strftime('%Y-%m-%d %H:%M:%S', localtime(time())) - print(f"[{now}] 结果文件【{result_file}】的数据已整理完成,保存文件需要1-2min,请耐心等待......") + now_doing_msg(result_file, 'done') + print(f"保存文件需要1-2min,请耐心等待......") wb_result.save(result_file) wb_result.close() @@ -223,7 +245,7 @@ def warn_pause_exit(msg, pause_num, exit_num): print(msg + '\n') for i in range(pause_num): _ = input("Press ENTER to continue......\n") - exit(exit_num) + sys.exit(exit_num) def check_files(raw_data_dirs, result_files): @@ -262,7 +284,7 @@ def check_files(raw_data_dirs, result_files): _, raw_data_files = traversal_files(raw_data_dir) for raw_data_file in raw_data_files: if raw_data_file.endswith(".xlsx"): - remove(raw_data_file) + os.remove(raw_data_file) _, raw_data_files = traversal_files(raw_data_dir) if len(raw_data_files) != 3: @@ -281,35 +303,32 @@ def delete_excel_files(raw_data_dirs): _, raw_data_files = traversal_files(raw_data_dir) for raw_data_file in raw_data_files: if raw_data_file.endswith('.xlsx'): - remove(raw_data_file) + os.remove(raw_data_file) def initialization(): - time_start = time() # 记录开始时间 + time_start = time.time() # 记录开始时间 try: # read init configurations from config file - wb_conf = load_workbook('./configuration.xlsx', read_only=True) - ws_conf = wb_conf['conf'] + wb_conf = openpyxl.load_workbook('./configs.xlsx', read_only=True) + ws_conf = wb_conf['brake'] - data_dir = ws_conf.cell(row=2, column=2).value + DATA_DIR = ws_conf.cell(row=2, column=2).value AV = int(ws_conf.cell(row=3, column=2).value) RR = int(ws_conf.cell(row=4, column=2).value) RC = float(ws_conf.cell(row=5, column=2).value) AXIS = ws_conf.cell(row=6, column=2).value wb_conf.close() - - raw_data_dirs, result_files = traversal_files(data_dir) - # print("#调试信息======================================") - # print(f"结果文件:{result_files}") - # print(f'数据目录:{raw_data_dirs}') - check_files(raw_data_dirs, result_files) - - return raw_data_dirs, result_files, time_start, AV, RR, RC, AXIS - except Exception as Err: - msg = "无法在当前路径下找到【configuration.xlsx】文件,请确认!" + msg = "无法在当前路径下找到或打开【configs.xlsx】文件,请确认!" warn_pause_exit(msg, 1, 2) + raw_data_dirs, result_files = traversal_files(DATA_DIR) + delete_excel_files(raw_data_dirs) + check_files(raw_data_dirs, result_files) + + return raw_data_dirs, result_files, time_start, AV, RR, RC, AXIS + def execution(args): raw_data_dirs, result_files, time_start, AV, RR, RC, AXIS = args @@ -318,17 +337,16 @@ def execution(args): prefix.append(raw_data_dir.split('\\')[-1].split("_")[0]) try: - threads = [] + # threads = [] for result_file in result_files: if result_file.split('\\')[-1].split('_')[0] not in set(prefix): continue else: - now = strftime('%Y-%m-%d %H:%M:%S', localtime(time())) - print(f"[{now}] 正在整理结果文件【{result_file}】的数据......") - # data_process(result_file, raw_data_dirs, AV, RR, RC, AXIS) - threads.append(Thread(target=data_process, args=(result_file, raw_data_dirs, AV, RR, RC, AXIS))) - [t.start() for t in threads] - [t.join() for t in threads] + now_doing_msg(result_file, 'start') + data_process(result_file, raw_data_dirs, AV, RR, RC, AXIS) + # threads.append(Thread(target=data_process, args=(result_file, raw_data_dirs, AV, RR, RC, AXIS))) + # [t.start() for t in threads] + # [t.join() for t in threads] print("#---------------------------------------------------------") print("全部处理完毕") delete_excel_files(raw_data_dirs) @@ -336,7 +354,7 @@ def execution(args): print("程序运行错误,请检查配置文件是否准确设定,以及数据文件组织是否正确!") delete_excel_files(raw_data_dirs) # 运行结束之后,删除中间临时文件 - time_end = time() # 记录结束时间 + time_end = time.time() # 记录结束时间 time_total = time_end - time_start # 计算的时间差为程序的执行时间,单位为秒/s msg = f"数据处理时间:{time_total//3600:02} h {time_total % 3600/60:05.2f} min" warn_pause_exit(msg, 1, 0) diff --git a/rokae/brake/configs.xlsx b/rokae/brake/configs.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..a23a93be3655c32e9584d2e6bfc2d8d51fa8715b GIT binary patch literal 10692 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