basically done

This commit is contained in:
2025-03-27 19:05:02 +08:00
parent 09d322378a
commit 957071075d
110 changed files with 2180 additions and 1797 deletions

0
codes/__init__.py Normal file
View File

View File

227
codes/analysis/brake.py Normal file
View File

@ -0,0 +1,227 @@
import json
import os.path
import time
import pandas
from PySide6.QtCore import Signal, QThread
import openpyxl
import re
from codes.common import clibs
class BrakeDataProcess(QThread):
output = Signal(str, str)
def __init__(self, dir_path, /):
super().__init__()
self.dir_path = dir_path
self.idx = 0
def logger(self, level, module, content, color="black", error="", flag="both"):
flag = "cursor" if level.upper() == "DEBUG" else "both"
clibs.logger(level, module, content, color, flag, signal=self.output)
if level.upper() == "ERROR":
raise Exception(f"{error} | {content}")
@clibs.handle_exception
def check_files(self, rawdata_dirs, result_files):
msg_wrong = "需要有四个文件和若干个数据文件夹,可参考如下确认:<br>"
msg_wrong += "- reach33/66/100_XXXXXXX.xlsx<br>- *.cfg<br>"
msg_wrong += "- reach33_load33_speed33<br>- reach33_load33_speed66<br>...<br>- reach100_load100_speed66<br>- reach100_load100_speed100<br>"
if len(result_files) != 4 or len(rawdata_dirs) == 0:
self.logger("ERROR", "brake", msg_wrong, "red", "InitFileError")
config_file, reach33_file, reach66_file, reach100_file = None, None, None, None
for result_file in result_files:
filename = result_file.split("/")[-1]
if re.match(".*\\.cfg", filename):
config_file = result_file
elif filename.startswith("reach33_") and filename.endswith(".xlsx"):
reach33_file = result_file
elif filename.startswith("reach66_") and filename.endswith(".xlsx"):
reach66_file = result_file
elif filename.startswith("reach100_") and filename.endswith(".xlsx"):
reach100_file = result_file
else:
if not (config_file and reach33_file and reach66_file and reach100_file):
self.logger("ERROR", "brake", msg_wrong, "red", "InitFileError")
reach_s = ['reach33', 'reach66', 'reach100']
load_s = ['load33', 'load66', 'load100']
speed_s = ['speed33', 'speed66', 'speed100']
prefix = []
for rawdata_dir in rawdata_dirs:
components = rawdata_dir.split("/")[-1].split('_') # reach_load_speed
prefix.append(components[0])
if components[0] not in reach_s or components[1] not in load_s or components[2] not in speed_s:
msg = f"报错信息:数据目录 {rawdata_dir} 命名不合规,请参考如下形式<br>"
msg += "命名规则reachAA_loadBB_speedCCAA/BB/CC 指的是臂展/负载/速度的比例<br>"
msg += "规则解释reach66_load100_speed33表示 66% 臂展100% 负载以及 33% 速度情况下的测试结果文件夹<br>"
self.logger("ERROR", "brake", msg, "red", "WrongDataFolder")
_, rawdata_files = clibs.traversal_files(rawdata_dir, self.output)
if len(rawdata_files) != 3:
msg = f"数据目录 {rawdata_dir} 下数据文件个数错误,每个数据目录下有且只能有三个以 .data 为后缀的数据文件"
self.logger("ERROR", "brake", msg, "red", "WrongDataFile")
for rawdata_file in rawdata_files:
if not rawdata_file.endswith(".data"):
msg = f"数据文件 {rawdata_file} 后缀错误,每个数据目录下有且只能有三个以 .data 为后缀的数据文件"
self.logger("ERROR", "brake", msg, "red", "WrongDataFile")
result_files = []
for _ in [reach33_file, reach66_file, reach100_file]:
if _.split("/")[-1].split("_")[0] in set(prefix):
result_files.append(_)
self.logger("INFO", "brake", "数据目录合规性检查结束,未发现问题......", "green")
return config_file, result_files
@clibs.handle_exception
def get_configs(self, config_file):
try:
with open(config_file, mode="r", encoding="utf-8") as f_config:
configs = json.load(f_config)
p_dir = config_file.split('/')[-2]
if not re.match("^[jJ][123]$", p_dir):
self.logger("ERROR", "brake", "被处理的根文件夹命名必须是 [Jj][123] 的格式", "red", "DirNameError")
axis = int(p_dir[-1]) # 要处理的轴
rrs = [abs(_) for _ in configs["TRANSMISSION"]["REDUCTION_RATIO_NUMERATOR"]] # 减速比rr for reduction ratio
avs = configs["MOTION"]["JOINT_MAX_SPEED"]
rr = rrs[axis-1]
av = avs[axis-1]
return av, rr
except Exception as Err:
self.logger("ERROR", "brake", f"无法打开 {config_file},或者使用了错误的机型配置文件,需检查<br>{Err}", "red", "OpenFileError")
def now_doing_msg(self, 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':
self.logger("INFO", "brake", f"[{now}] 正在处理目录 {docs} 中的数据......")
elif flag == 'start' and file_type == 'file':
self.logger("INFO", "brake", f"[{now}] 正在处理文件 {docs} 中的数据......")
elif flag == 'done' and file_type == 'dir':
self.logger("INFO", "brake", f"[{now}] 目录 {docs} 数据文件已处理完毕")
elif flag == 'done' and file_type == 'file':
self.logger("INFO", "brake", f"[{now}] 文件 {docs} 数据已处理完毕")
@staticmethod
@clibs.handle_exception
def data2result(df, ws_result, row_start, row_end):
data = []
for row in range(row_start, row_end):
data.append(df.iloc[row, 0])
data.append(df.iloc[row, 1])
data.append(df.iloc[row, 2])
i = 0
row_max = 1000 if row_end - row_start < 1000 else row_end - row_start + 100
for row in range(2, row_max):
try:
ws_result.cell(row=row, column=1).value = data[i]
ws_result.cell(row=row, column=2).value = data[i + 1]
ws_result.cell(row=row, column=3).value = data[i + 2]
i += 3
except Exception:
ws_result.cell(row=row, column=1).value = None
ws_result.cell(row=row, column=2).value = None
ws_result.cell(row=row, column=3).value = None
@clibs.handle_exception
def get_row_range(self, data_file, df, conditions, av, rr):
row_start, row_end = 0, 0
ratio = float(conditions[2].removeprefix('speed')) / 100
av_max = av * ratio
threshold = 0.95
for row in range(df.index[-1] - 1, -1, -10):
if df.iloc[row, 2] != 0:
row_start = row - 20 if row - 20 > 0 else 0 # 急停前找 20 个点
break
else:
self.logger("ERROR", "brake", f"数据文件 {data_file} 采集的数据中没有 ESTOP 为非 0 的情况,需要确认", "red", "StartNotFoundError")
for row in range(row_start, df.index[-1] - 1, 10):
speed_row = df.iloc[row, 0] * clibs.RADIAN * rr * 60 / 360
if abs(speed_row) < 1:
row_end = row + 100 if row + 100 <= df.index[-1] - 1 else df.index[-1] - 1
break
else:
self.logger("ERROR", "brake", f"数据文件 {data_file} 最后的速度未降为零", "red", "SpeedNotZeroError")
av_estop = abs(df.iloc[row_start - 20:row_start, 0].abs().mean() * clibs.RADIAN)
if abs(av_estop / av_max) < threshold:
filename = data_file.split("/")[-1]
msg = f"[av_estop: {av_estop:.2f} | shouldbe: {av_max:.2f}] 数据文件 {filename} 触发 ESTOP 时未采集到指定百分比的最大速度,需要检查"
self.logger("WARNING", "brake", msg, "#8A2BE2")
return row_start, row_end
@staticmethod
@clibs.handle_exception
def get_shtname(conditions, count):
# 33%负载_33%速度_1 - reach/load/speed
load = conditions[1].removeprefix('load')
speed = conditions[2].removeprefix('speed')
result_sheet_name = f"{load}%负载_{speed}%速度_{count}"
return result_sheet_name
@clibs.handle_exception
def single_file_process(self, data_file, wb, count, av, rr):
df = pandas.read_csv(data_file, sep='\t')
conditions = data_file.split("/")[-2].split("_") # reach/load/speed
shtname = self.get_shtname(conditions, count)
ws = wb[shtname]
row_start, row_end = self.get_row_range(data_file, df, conditions, av, rr)
self.data2result(df, ws, row_start, row_end)
@clibs.handle_exception
def data_process(self, result_file, rawdata_dirs, av, rr):
filename = result_file.split("/")[-1]
self.logger("INFO", "brake", f"正在打开文件 {filename},这可能需要一些时间......", "blue")
try:
wb = openpyxl.load_workbook(result_file)
except Exception as Err:
self.logger("ERROR", "brake", f"{filename}文件打开失败,可能是文件已损坏,确认后重新执行!<br>{Err}", "red", "CannotOpenFile")
prefix = filename.split('_')[0]
for rawdata_dir in rawdata_dirs:
if rawdata_dir.split("/")[-1].split('_')[0] == prefix:
self.now_doing_msg(rawdata_dir, 'start')
_, data_files = clibs.traversal_files(rawdata_dir, self.output)
for idx in range(3):
self.single_file_process(data_files[idx], wb, idx+1, av, rr)
# threads = [
# threading.Thread(target=self.single_file_process, args=(data_files[0], wb, 1, av, rr)),
# threading.Thread(target=self.single_file_process, args=(data_files[1], wb, 2, av, rr)),
# threading.Thread(target=self.single_file_process, args=(data_files[2], wb, 3, av, rr))
# ]
# [t.start() for t in threads]
# [t.join() for t in threads]
self.now_doing_msg(rawdata_dir, 'done')
self.logger("INFO", "brake", f"正在保存文件 {filename},这可能需要一些时间......<br>", "blue")
wb.save(result_file)
wb.close()
@clibs.handle_exception
def processing(self):
time_start = time.time()
clibs.running[self.idx] = 1
rawdata_dirs, result_files = clibs.traversal_files(self.dir_path, self.output)
config_file, result_files = self.check_files(rawdata_dirs, result_files)
av, rr = self.get_configs(config_file)
for result_file in result_files:
self.data_process(result_file, rawdata_dirs, av, rr)
self.logger("INFO", "brake", "-"*60 + "<br>全部处理完毕<br>", "purple")
time_total = time.time() - time_start
msg = f"处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s"
self.logger("INFO", "brake", msg)

436
codes/analysis/current.py Normal file
View File

@ -0,0 +1,436 @@
import json
import openpyxl
import pandas
import re
import csv
from PySide6.QtCore import Signal, QThread
import time
from codes.common import clibs
class CurrentDataProcess(QThread):
output = Signal(str, str)
def __init__(self, dir_path, proc, /):
super().__init__()
self.dir_path = dir_path
self.proc = proc
self.idx = 1
def logger(self, level, module, content, color="black", error="", flag="both"):
flag = "cursor" if level.upper() == "DEBUG" else "both"
clibs.logger(level, module, content, color, flag, signal=self.output)
if level.upper() == "ERROR":
raise Exception(f"{error} | {content}")
@clibs.handle_exception
def initialization(self):
_, data_files = clibs.traversal_files(self.dir_path, self.output)
count, config_file = 0, None
for data_file in data_files:
filename = data_file.split("/")[-1]
if re.match(".*\\.cfg", filename):
config_file = data_file
count += 1
elif filename == "T_电机电流.xlsx":
count += 1
else:
if not re.match("^j[1-7].*\\.data$", filename):
msg = f"不合规 {data_file}<br>"
msg += "所有数据文件必须以 j[1-7]_ 开头,以 .data 结尾比如j1_abcdef.data请检查整改后重新运行"
self.logger("ERROR", "current", msg, "red", "FilenameIllegal")
if count != 2:
msg = "需要有一个机型配置文件\"*.cfg\",以及一个数据处理文件\"T_电机电流.xlsx\"表格,请检查整改后重新运行"
self.logger("ERROR", "current", msg, "red", "FilenameIllegal")
return data_files, config_file
@clibs.handle_exception
def current_max(self, data_files, rts):
self.logger("INFO", "current", f"正在处理最大转矩值逻辑......")
current = {1: [], 2: [], 3: [], 4: [], 5: [], 6: []}
for data_file in data_files:
if data_file.endswith(".data"):
df = pandas.read_csv(data_file, sep="\t")
else:
continue
self.logger("DEBUG", "current", f"正在处理 {data_file} ...")
cols = len(df.columns)
axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j"))
rt = rts[axis-1]
self.logger("DEBUG", "current", f"最大列数为 {cols}{axis} 轴的额定转矩为 {rt}")
col = df.columns.values[clibs.c_servo_trq-1] # 获取 "device_servo_trq_feedback"
c_max = df[col].abs().max()
scale = 1000
_ = abs(c_max/scale*rt)
current[axis].append(_)
self.logger("DEBUG", "current", f"{data_file}: {_:.2f}")
self.logger("DEBUG", "current", f"获取到的列名为 {col},最大转矩为 {_}")
with open(data_file, "a+") as f_data:
csv_writer = csv.writer(f_data, delimiter="\t")
csv_writer.writerow([""] * (cols-1) + [_])
for axis, cur in current.items():
if not cur:
continue
else:
_ = ""
for value in cur:
_ += f"{value:.4f} "
self.logger("INFO", "current", f"{axis}轴最大转矩数据:{_}")
self.logger("DEBUG", "current", f"获取最大转矩值结束 current_max = {current}")
self.logger("INFO", "current", f"最大转矩数据处理完毕......")
return current
@clibs.handle_exception
def current_avg(self, data_files, rts):
self.logger("INFO", "current", f"正在处理平均转矩值逻辑......")
current = {1: [], 2: [], 3: [], 4: [], 5: [], 6: []}
for data_file in data_files:
if data_file.endswith(".data"):
df = pandas.read_csv(data_file, sep="\t")
else:
continue
self.logger("DEBUG", "current", f"正在处理 {data_file} ...")
cols = len(df.columns)
axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j"))
rt = rts[axis-1]
self.logger("DEBUG", "current", f"最大列数为 {cols}{axis} 轴的额定转矩为 {rt}")
col = df.columns.values[clibs.c_servo_trq-1]
c_std = df[col].std()
c_avg = df[col].mean()
scale = 1000
_ = (abs(c_avg)+c_std*3)/scale*rt
current[axis].append(_)
self.logger("DEBUG", "current", f"{data_file}: {_:.2f}")
self.logger("DEBUG", "current", f"获取到的列名为 {col},平均转矩为 {_}")
with open(data_file, "a+") as f_data:
csv_writer = csv.writer(f_data, delimiter="\t")
csv_writer.writerow([""] * (cols-1) + [_])
for axis, cur in current.items():
if not cur:
continue
else:
_ = ""
for value in cur:
_ += f"{value:.4f} "
self.logger("INFO", "current", f"{axis}轴平均转矩数据:{_}")
self.logger("DEBUG", "current", f"获取平均转矩值结束 current_avg = {current}")
self.logger("INFO", "current", f"平均转矩数据处理完毕......")
return current
@clibs.handle_exception
def current_cycle(self, data_files, rrs, rts, params):
result, hold, single, scenario, dur_time = None, [], [], [], 0
for data_file in data_files:
filename = data_file.split("/")[-1]
if filename == "T_电机电流.xlsx":
result = data_file
elif re.match("j[1-7]_hold_.*\\.data", filename):
hold.append(data_file)
elif re.match("j[1-7]_s_.*\\.data", filename):
scenario.append(data_file)
dur_time = float(filename.split("_")[3])
elif re.match("j[1-7]_.*\\.data", filename):
single.append(data_file)
clibs.stop, filename = True, result.split("/")[-1]
self.logger("INFO", "current", f"正在打开文件 {filename},这可能需要一些时间......", "blue")
try:
wb = openpyxl.load_workbook(result)
except Exception as Err:
self.logger("ERROR", "current", f"{filename}文件打开失败,可能是文件已损坏,确认后重新执行!<br>{Err}", "red", "CannotOpenFile")
ws = wb["统计"]
for idx in range(len(params)-1):
row = idx + 2
for col in range(2, 8):
ws.cell(row=row, column=col).value = params[idx][col-2]
ws.cell(row=1, column=1).value = params[-1]
if hold:
avg = self.current_avg(hold, rts)
for axis, cur_value in avg.items():
sht_name = f"J{axis}"
wb[sht_name]["P4"].value = float(cur_value[0])
if dur_time == 0:
self.p_single(wb, single, rrs)
else:
self.p_scenario(wb, scenario, rrs, dur_time)
self.logger("INFO", "current", f"正在保存文件 {filename},这可能需要一些时间......", "blue")
wb.save(result)
wb.close()
@clibs.handle_exception
def find_point(self, data_file, df, flag, row_s, row_e, threshold, step, end_point, skip_scale, axis, seq):
if flag == "lt":
while row_e > end_point:
speed_avg = df.iloc[row_s:row_e].abs().mean()
if speed_avg < threshold:
row_e -= step
row_s -= step
continue
else:
# one more time如果连续两次 200 个点的平均值都大于 threshold说明已经到了临界点了其实也不一定只不过相对遇到一次就判定临界点更安全一点点
# 从实际数据看,这开逻辑很小概率能触发到
speed_avg = df.iloc[row_s-end_point*skip_scale:row_e-end_point*skip_scale].abs().mean()
if speed_avg < threshold:
self.logger("WARNING", "current", f"【lt】{axis} 轴第 {seq} 次查找数据可能有异常row_s = {row_s}, row_e = {row_e}", "purple")
return row_s, row_e
else:
self.logger("ERROR", "current", f"{data_file} 数据有误,需要检查,无法找到第 {seq} 个有效点......", "red", "AnchorNotFound")
elif flag == "gt":
while row_e > end_point:
speed_avg = df.iloc[row_s:row_e].abs().mean()
if speed_avg > threshold:
row_e -= step
row_s -= step
continue
else:
# one more time如果连续两次 200 个点的平均值都小于 threshold说明已经到了临界点了其实也不一定只不过相对遇到一次就判定临界点更安全一点点
# 从实际数据看,这开逻辑很小概率能触发到
speed_avg = df.iloc[row_s-end_point*skip_scale:row_e-end_point*skip_scale].abs().mean()
if speed_avg > threshold:
self.logger("WARNING", "current", f"【gt】{axis} 轴第 {seq} 次查找数据可能有异常row_s = {row_s}, row_e = {row_e}", "purple")
return row_s, row_e
else:
self.logger("ERROR", "current", f"{data_file} 数据有误,需要检查,无法找到第 {seq} 个有效点......", "red", "AnchorNotFound")
@clibs.handle_exception
def get_row_number(self, threshold, flag, df, row_s, row_e, axis):
count_1, count_2 = 0, 0
if flag == "start" or flag == "end":
for number in df.iloc[row_s:row_e].abs():
count_2 += 1
if number > threshold:
count_1 += 1
if count_1 == 10:
return row_s + count_2 - 10
else:
count_1 = 0
elif flag == "middle":
for number in df.iloc[row_s:row_e].abs():
count_2 += 1
if number < threshold: # 唯一的区别
count_1 += 1
if count_1 == 10:
return row_s + count_2 - 10
else:
count_1 = 0
places = {"start": "起点", "middle": "中间点", "end": "终点"} # 因为是终点数据,所以可能有异常
self.logger("DEBUG", "current", f"{axis} 轴获取{places[flag]}数据 {row_e} 可能有异常,需关注!", "purple")
return row_e
@clibs.handle_exception
def p_single(self, wb, single, rrs):
# 1. 先找到第一个速度为零的点,数据从后往前找,一开始就是零的情况不予考虑
# 2. 记录第一个点的位置,继续向前查找第二个速度为零的点,同理,一开始为零的点不予考虑
# 3. 记录第二个点的位置,并将其中的数据拷贝至对应位置
for data_file in single:
axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j"))
sht_name = f"J{axis}"
ws = wb[sht_name]
pandas.set_option("display.precision", 2)
df_origin = pandas.read_csv(data_file, sep="\t")
rr = rrs[axis-1]
addition = 180 / 3.1415926 * 60 / 360 * rr
col_names = list(df_origin.columns)
df = df_origin[col_names[clibs.c_joint_vel-1]].multiply(addition)
step = 50 # 步进值
end_point = 200 # 有效数值的数目
threshold = 5 # 200个点的平均阈值线
skip_scale = 2
row_start, row_middle, row_end = 0, 0, 0
row_e = df.index[-1]
row_s = row_e - end_point
speed_avg = df.iloc[row_s:row_e].abs().mean()
if speed_avg < threshold:
# 第一次过滤:消除速度为零的数据,找到速度即将大于零的上升临界点
row_s, row_e = self.find_point(data_file, df, "lt", row_s, row_e, threshold, step, end_point, skip_scale, axis, "pre-1")
row_e -= end_point*skip_scale
row_s -= end_point*skip_scale
# 第二次过滤:消除速度大于零的数据,找到速度即将趋近于零的下降临界点
row_s, row_e = self.find_point(data_file, df, "gt", row_s, row_e, threshold, step, end_point, skip_scale, axis, "pre-2")
row_e -= end_point*skip_scale
row_s -= end_point*skip_scale
# 第三次过滤:消除速度为零的数据,找到速度即将大于零的上升临界点
row_s, row_e = self.find_point(data_file, df, "lt", row_s, row_e, threshold, step, end_point, skip_scale, axis, "pre-3")
row_e -= end_point*skip_scale
row_s -= end_point*skip_scale
# 正式第一次采集:消除速度大于零的数据,找到速度即将趋近于零的下降临界点
row_s, row_e = self.find_point(data_file, df, "gt", row_s, row_e, threshold, step, end_point, skip_scale, axis, 1)
row_end = self.get_row_number(threshold, "end", df, row_s, row_e, axis)
row_e -= end_point*skip_scale
row_s -= end_point*skip_scale
# 正式第二次采集:消除速度为零的数据,找到速度即将大于零的上升临界点
row_s, row_e = self.find_point(data_file, df, "lt", row_s, row_e, threshold, step, end_point, skip_scale, axis, 2)
row_middle = self.get_row_number(threshold, "middle", df, row_s, row_e, axis)
row_e -= end_point*skip_scale
row_s -= end_point*skip_scale
# 正式第三次采集:消除速度大于零的数据,找到速度即将趋近于零的下降临界点
row_s, row_e = self.find_point(data_file, df, "gt", row_s, row_e, threshold, step, end_point, skip_scale, axis, 3)
row_start = self.get_row_number(threshold, "start", df, row_s, row_e, axis)
elif speed_avg > threshold:
# 第一次过滤:消除速度大于零的数据,找到速度即将趋近于零的下降临界点
row_s, row_e = self.find_point(data_file, df, "gt", row_s, row_e, threshold, step, end_point, skip_scale, axis, "pre-1")
row_e -= end_point*skip_scale
row_s -= end_point*skip_scale
# 第二次过滤:消除速度为零的数据,找到速度即将大于零的上升临界点
row_s, row_e = self.find_point(data_file, df, "lt", row_s, row_e, threshold, step, end_point, skip_scale, axis, "pre-2")
row_e -= end_point*skip_scale
row_s -= end_point*skip_scale
# 第一次正式采集:消除速度大于零的数据,找到速度即将趋近于零的下降临界点
row_s, row_e = self.find_point(data_file, df, "gt", row_s, row_e, threshold, step, end_point, skip_scale, axis, 1)
row_end = self.get_row_number(threshold, "end", df, row_s, row_e, axis)
row_e -= end_point*skip_scale
row_s -= end_point*skip_scale
# 第二次正式采集:消除速度为零的数据,找到速度即将大于零的上升临界点
row_s, row_e = self.find_point(data_file, df, "lt", row_s, row_e, threshold, step, end_point, skip_scale, axis, 2)
row_middle = self.get_row_number(threshold, "middle", df, row_s, row_e, axis)
row_e -= end_point*skip_scale
row_s -= end_point*skip_scale
# 第三次正式采集:消除速度大于零的数据,找到速度即将趋近于零的下降临界点
row_s, row_e = self.find_point(data_file, df, "gt", row_s, row_e, threshold, step, end_point, skip_scale, axis, 3)
row_start = self.get_row_number(threshold, "start", df, row_s, row_e, axis)
self.logger("DEBUG", "current", f"{axis} 轴起点:{row_start}")
self.logger("DEBUG", "current", f"{axis} 轴中间点:{row_middle}")
self.logger("DEBUG", "current", f"{axis} 轴终点:{row_end}")
self.logger("DEBUG", "current", f"{axis} 轴数据非零段点数:{row_middle-row_start+1}")
self.logger("DEBUG", "current", f"{axis} 轴数据为零段点数:{row_end-row_middle+1}")
if abs(row_end+row_start-2*row_middle) > 1000:
self.logger("DEBUG", "current", f"{axis} 轴数据占空比异常!", "purple")
data, first_c, second_c, third_c, fourth_c = [], clibs.c_joint_vel-1, clibs.c_servo_trq-1, clibs.c_sensor_trq-1, clibs.c_estimate_trans_trq-1
for row in range(row_start, row_end+1):
data.append(df_origin.iloc[row, first_c])
data.append(df_origin.iloc[row, second_c])
data.append(df_origin.iloc[row, third_c])
data.append(df_origin.iloc[row, fourth_c])
i = 0
for row in ws.iter_rows(min_row=2, min_col=2, max_row=150000, max_col=5):
for cell in row:
try:
if i % 4 == 0:
ws.cell((i//4)+2, 1).value = float(((i//4)+1)/1000)
_ = f"{data[i]:.2f}"
cell.value = float(_)
i += 1
except Exception:
if i % 4 == 0:
ws.cell((i//4)+2, 1).value = None
cell.value = None
i += 1
@clibs.handle_exception
def p_scenario(self, wb, scenario, rrs, dur_time):
self.logger("INFO", "current", f"本次处理的是电机电流场景数据,场景运动周期为 {dur_time}s", "blue")
for data_file in scenario:
cycle = 0.001
axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j"))
sht_name = f"J{axis}"
ws = wb[sht_name]
pandas.set_option("display.precision", 2)
df_origin = pandas.read_csv(data_file, sep="\t")
rr = rrs[axis-1]
addition = 180 / 3.1415926 * 60 / 360 * rr
col_names = list(df_origin.columns)
df = df_origin[col_names[clibs.c_joint_vel-1]].multiply(addition)
row_start = 3000
row_end = row_start + int(dur_time/cycle)
if row_end > df.index[-1]:
self.logger("ERROR", "current", f"位置超限:{data_file} 共有 {df.index[-1]} 条数据,无法取到第 {row_end} 条数据,需要确认场景周期时间...", "blue", "DataOverLimit")
data, first_c, second_c, third_c, fourth_c = [], clibs.c_joint_vel-1, clibs.c_servo_trq-1, clibs.c_sensor_trq-1, clibs.c_estimate_trans_trq-1
for row in range(row_start, row_end+1):
data.append(df_origin.iloc[row, first_c])
data.append(df_origin.iloc[row, second_c])
data.append(df_origin.iloc[row, third_c])
data.append(df_origin.iloc[row, fourth_c])
i = 0
for row in ws.iter_rows(min_row=2, min_col=2, max_row=250000, max_col=5):
for cell in row:
try:
if i % 4 == 0:
ws.cell((i//4)+2, 1).value = float(((i//4)+1)/1000)
_ = f"{data[i]:.2f}"
cell.value = float(_)
i += 1
except Exception:
cell.value = None
if i % 4 == 0:
ws.cell((i//4)+2, 1).value = None
i += 1
@clibs.handle_exception
def get_configs(self, config_file):
try:
if re.match("^[NXEC]B.*", config_file.split("/")[-1]):
robot_type = "工业"
else:
robot_type = "协作"
with open(config_file, mode="r", encoding="utf-8") as f_config:
configs = json.load(f_config)
version = configs["VERSION"]
sc = [0.001, 0.001, 0.001, 0.001, 0.001, 0.001] # 采样周期sc for sample cycle
r_rrs = configs["TRANSMISSION"]["REDUCTION_RATIO_NUMERATOR"] # 减速比rr for reduction ratio
m_avs = configs["MOTION"]["JOINT_MAX_SPEED"]
m_stall_ts = configs["MOTOR"]["STALL_TORQUE"] # 电机堵转转矩
m_rts = configs["MOTOR"]["RATED_TORQUE"] # 电机额定转矩rt for rated torque
m_max_ts = configs["MOTOR"]["PEAK_TORQUE"] # 电机峰值转矩
m_r_rpms = configs["MOTOR"]["RATED_SPEED"] # 电机额定转速
m_max_rpms = configs["MOTOR"]["MAX_SPEED"] # 电机最大转速
r_max_sst = configs["TRANSMISSION"]["MAX_TORQUE_FOR_START_AND_STOP"] # 减速器最大启停转矩sst for start and stop torque
r_max_t = configs["TRANSMISSION"]["MAX_PEAK_TORQUE"] # 减速器瞬时最大转矩
r_avg_t = configs["TRANSMISSION"]["MAX_AVERAGE_TORQUE"] # 减速器平均负载转矩允许最大值
self.logger("INFO", "current", f"get_configs: 机型文件版本 {config_file}_{version}")
self.logger("INFO", "current", f"get_configs: 减速比 {r_rrs}")
self.logger("INFO", "current", f"get_configs: 额定转矩 {m_rts}")
self.logger("INFO", "current", f"get_configs: 最大角速度 {m_avs}")
return sc, r_rrs, m_avs, m_stall_ts, m_rts, m_max_ts, m_r_rpms, m_max_rpms, r_max_sst, r_max_t, r_avg_t, robot_type
except Exception as Err:
self.logger("ERROR", "current", f"get_config: 无法打开 {config_file},或获取配置文件参数错误 {Err}", "red", "OpenFileError")
@clibs.handle_exception
def processing(self):
time_start = time.time()
clibs.running[self.idx] = 1
data_files, config_file = self.initialization()
params = self.get_configs(config_file)
rts, rrs = params[4], params[1]
if self.proc == "最大值":
self.current_max(data_files, rts)
elif self.proc == "平均值":
self.current_avg(data_files, rts)
elif self.proc == "周期":
self.current_cycle(data_files, rrs, rts, params)
self.logger("INFO", "current", "-"*60 + "<br>全部处理完毕<br>", "purple")
time_total = time.time() - time_start
msg = f"数据处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s\n"
self.logger("INFO", "current", msg)

214
codes/analysis/iso.py Normal file
View File

@ -0,0 +1,214 @@
import pdfplumber
import openpyxl
import os
import time
from PySide6.QtCore import Signal, QThread
from codes.common import clibs
class IsoDataProcess(QThread):
output = Signal(str, str)
def __init__(self, dir_path, /):
super().__init__()
self.dir_path = dir_path
self.idx = 2
def logger(self, level, module, content, color="black", error="", flag="both"):
flag = "cursor" if level.upper() == "DEBUG" else "both"
clibs.logger(level, module, content, color, flag, signal=self.output)
if level.upper() == "ERROR":
raise Exception(f"{error} | {content}")
def p_iso(self, file, p_files, ws, tmpfile):
p_files.append(file)
pdf = pdfplumber.open(file)
with open(tmpfile, mode="w", encoding="utf-8") as fb:
for page in pdf.pages:
fb.write(page.extract_text())
with open(tmpfile, mode="r", encoding="utf-8") as fb:
lines = fb.readlines()
lines = [line for line in lines if not line.startswith("Page ")]
for line in lines:
if line.strip() == "Pose Accuracy and Repeatability":
index = lines.index(line)
ws.cell(row=3, column=7).value = float(lines[index+4].split()[1])
ws.cell(row=4, column=7).value = float(lines[index+5].split()[1])
ws.cell(row=5, column=7).value = float(lines[index+6].split()[1])
ws.cell(row=6, column=7).value = float(lines[index+7].split()[1])
ws.cell(row=7, column=7).value = float(lines[index+8].split()[1])
ws.cell(row=8, column=7).value = float(lines[index+4].split()[2])
ws.cell(row=9, column=7).value = float(lines[index+5].split()[2])
ws.cell(row=10, column=7).value = float(lines[index+6].split()[2])
ws.cell(row=11, column=7).value = float(lines[index+7].split()[2])
ws.cell(row=12, column=7).value = float(lines[index+8].split()[2])
elif line.strip() == "Pose Accuracy Variation":
index = lines.index(line)
ws.cell(row=13, column=7).value = float(lines[index+4].split()[1])
ws.cell(row=14, column=7).value = float(lines[index+5].split()[1])
ws.cell(row=15, column=7).value = float(lines[index+6].split()[1])
elif line.strip() == "Distance Accuracy":
index = lines.index(line)
ws.cell(row=16, column=7).value = float(lines[index + 4].split()[1])
ws.cell(row=17, column=7).value = float(lines[index + 4].split()[2])
elif line.strip() == "Stabilisation Time and Overshoot":
index = lines.index(line)
ws.cell(row=18, column=7).value = float(lines[index + 7].split()[3])
ws.cell(row=19, column=7).value = float(lines[index + 7].split()[2])
elif line.strip() == "Velocity Accuracy and Repeatability":
index = lines.index(line)
ws.cell(row=20, column=7).value = float(lines[index + 4].split()[1])
ws.cell(row=21, column=7).value = float(lines[index + 4].split()[2])
ws.cell(row=22, column=7).value = float(lines[index + 4].split()[3])
elif line.strip()[:31] == "Path Accuracy and Repeatability":
index = lines.index(line)
ws.cell(row=29, column=7).value = float(lines[index + 4].split()[1])
ws.cell(row=30, column=7).value = float(lines[index + 4].split()[2])
elif line.strip() == "Corner Overshoot and Roundoff":
index = lines.index(line)
ws.cell(row=35, column=7).value = float(lines[index + 4].split()[1])
ws.cell(row=36, column=7).value = float(lines[index + 4].split()[2])
elif line.strip() == "Robot Weaving":
index = lines.index(line)
ws.cell(row=41, column=7).value = float(lines[index + 4].split()[2])
ws.cell(row=42, column=7).value = float(lines[index + 4].split()[3])
ws.cell(row=43, column=7).value = float(lines[index + 4].split()[4])
else:
pass
pdf.close()
def p_iso_100(self, file, p_files, ws, tmpfile):
p_files.append(file)
pdf = pdfplumber.open(file)
with open(tmpfile, mode="w", encoding="utf-8") as fb:
for page in pdf.pages:
fb.write(page.extract_text())
with open(tmpfile, mode="r", encoding="utf-8") as fb:
lines = fb.readlines()
lines = [line for line in lines if not line.startswith("Page ")]
for line in lines:
if line.strip() == "Velocity Accuracy and Repeatability":
index = lines.index(line)
ws.cell(row=26, column=7).value = float(lines[index + 4].split()[1])
ws.cell(row=27, column=7).value = float(lines[index + 4].split()[2])
ws.cell(row=28, column=7).value = float(lines[index + 4].split()[3])
elif line.strip()[:31] == "Path Accuracy and Repeatability":
index = lines.index(line)
ws.cell(row=33, column=7).value = float(lines[index + 4].split()[1])
ws.cell(row=34, column=7).value = float(lines[index + 4].split()[2])
elif line.strip() == "Corner Overshoot and Roundoff":
index = lines.index(line)
ws.cell(row=39, column=7).value = float(lines[index + 4].split()[1])
ws.cell(row=40, column=7).value = float(lines[index + 4].split()[2])
elif line.strip() == "Robot Weaving":
index = lines.index(line)
ws.cell(row=47, column=7).value = float(lines[index + 4].split()[2])
ws.cell(row=48, column=7).value = float(lines[index + 4].split()[3])
ws.cell(row=49, column=7).value = float(lines[index + 4].split()[4])
else:
pass
pdf.close()
def p_iso_1000(self, file, p_files, ws, tmpfile):
p_files.append(file)
pdf = pdfplumber.open(file)
with open(tmpfile, mode="w", encoding="utf-8") as fb:
for page in pdf.pages:
fb.write(page.extract_text())
with open(tmpfile, mode="r", encoding="utf-8") as fb:
lines = fb.readlines()
lines = [line for line in lines if not line.startswith("Page ")]
for line in lines:
if line.strip() == "Velocity Accuracy and Repeatability":
index = lines.index(line)
ws.cell(row=23, column=7).value = float(lines[index + 4].split()[1])
ws.cell(row=24, column=7).value = float(lines[index + 4].split()[2])
ws.cell(row=25, column=7).value = float(lines[index + 4].split()[3])
elif line.strip()[:31] == "Path Accuracy and Repeatability":
index = lines.index(line)
ws.cell(row=31, column=7).value = float(lines[index + 4].split()[1])
ws.cell(row=32, column=7).value = float(lines[index + 4].split()[2])
elif line.strip() == "Corner Overshoot and Roundoff":
index = lines.index(line)
ws.cell(row=37, column=7).value = float(lines[index + 4].split()[1])
ws.cell(row=38, column=7).value = float(lines[index + 4].split()[2])
elif line.strip() == "Robot Weaving":
index = lines.index(line)
ws.cell(row=44, column=7).value = float(lines[index + 4].split()[2])
ws.cell(row=45, column=7).value = float(lines[index + 4].split()[3])
ws.cell(row=46, column=7).value = float(lines[index + 4].split()[4])
else:
pass
pdf.close()
def initialization(self):
dirs, files = clibs.traversal_files(self.dir_path, self.output)
if len(dirs) != 0:
self.logger("ERROR", "iso", f"init: 工作目录下不可以有文件夹!", "red", "InitFileError")
for file in files:
file = file.lower()
if file.endswith("iso-results.xlsx"):
pass
elif file.endswith("iso-v1000.pdf"):
pass
elif file.endswith("iso-v100.pdf"):
pass
elif file.endswith("iso.pdf"):
pass
else:
self.logger("ERROR", "iso", f"init: 工作目录下只允许有如下四个文件不区分大小写pdf文件最少有一个<br>1. iso-results.xlsx<br>2. ISO.pdf<br>3. ISO-V100.pdf<br>4. ISO-V1000.pdf", "red", "InitFileError")
return files
def processing(self):
time_start = time.time()
clibs.running[self.idx] = 1
files = self.initialization()
filename = f"{self.dir_path}/iso-results.xlsx"
tmpfile = f"{self.dir_path}/data.txt"
wb, ws = None, None
try:
wb = openpyxl.load_workbook(filename)
ws = wb.active
for i in range(3, 50):
ws.cell(row=i, column=7).value = None
except Exception as Err:
self.logger("ERROR", "iso", f"main: 无法打开文件 {filename}<br>{Err}", "red", "FileOpenError")
p_files = []
for file in files:
if file.split("/")[-1].lower() == "iso.pdf":
self.logger("INFO", "iso", f"正在处理{file}......")
self.p_iso(file, p_files, ws, tmpfile)
self.logger("INFO", "iso", f"文件{file}已处理完毕。")
elif file.split("/")[-1].lower() == "iso-v100.pdf":
self.logger("INFO", "iso", f"正在处理{file}......")
self.p_iso_100(file, p_files, ws, tmpfile)
self.logger("INFO", "iso", f"文件{file}已处理完毕。")
elif file.split("/")[-1].lower() == "iso-v1000.pdf":
self.logger("INFO", "iso", f"正在处理{file}......")
self.p_iso_1000(file, p_files, ws, tmpfile)
self.logger("INFO", "iso", f"文件{file}已处理完毕。")
else:
pass
wb.save(filename)
wb.close()
if len(p_files) == 0:
self.logger("ERROR", "iso", f"目录 {self.dir_path} 下没有需要处理的文件,需要确认......", "red", "FileNotFound")
else:
os.remove(tmpfile)
self.logger("INFO", "current-processing", "-" * 60 + "<br>全部处理完毕<br>", "purple")
time_total = time.time() - time_start
msg = f"数据处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s\n"
self.logger("INFO", "current-processing", msg)

View File

@ -0,0 +1,161 @@
import pandas
import csv
import openpyxl
import chardet
import time
from PySide6.QtCore import Signal, QThread
from codes.common import clibs
class WaveloggerDataProcess(QThread):
output = Signal(str, str)
def __init__(self, dir_path, /):
super().__init__()
self.dir_path = dir_path
self.idx = 3
def logger(self, level, module, content, color="black", error="", flag="both"):
flag = "cursor" if level.upper() == "DEBUG" else "both"
clibs.logger(level, module, content, color, flag, signal=self.output)
if level.upper() == "ERROR":
raise Exception(f"{error} | {content}")
def find_point(self, bof, step, margin, threshold, pos, data_file, flag, df, row):
# bof: backward or forward
# pos: used for debug
# flag: greater than or lower than
row_target = None
row_origin = len(df) - margin + 1
if flag == "gt":
while 0 < row < row_origin:
value = float(df.iloc[row, 2])
if value > threshold:
row = row - step if bof == "backward" else row + step
continue
else:
row_target = row - step if bof == "backward" else row + step
break
else:
if bof == "backward":
self.logger("ERROR", "wavelogger", f"find_point-gt: [{pos}] 在 {data_file} 中,无法正确识别数据,需要确认...", "red", "DataError")
elif bof == "forward":
row_target = row + margin # to end while loop in function `single_file_proc`
elif flag == "lt":
while 0 < row < row_origin:
value = float(df.iloc[row, 2])
if value < threshold:
row = row - step if bof == "backward" else row + step
continue
else:
row_target = row - step if bof == "backward" else row + step
break
else:
if bof == "backward":
self.logger("ERROR", "wavelogger", f"find_point-lt: [{pos}] 在 {data_file} 中,无法正确识别数据,需要确认...", "red", "DataError")
elif bof == "forward":
row_target = row + margin # to end while loop in function `single_file_proc`
return row_target
def get_cycle_info(self, data_file, step, margin, threshold):
# end -> middle: low
# middle -> start: high
# 1. 从最后读取数据无论是大于1还是小于1都舍弃找到相反的值的起始点
# 2. 从起始点,继续往前寻找,找到与之数值相反的中间点
# 3. 从中间点,继续往前寻找,找到与之数值相反的结束点,至此,得到了高低数值的时间区间以及一轮的周期时间
with open(data_file, "rb") as f:
raw_data = f.read(1000)
result = chardet.detect(raw_data)
encoding = result['encoding']
csv_reader = csv.reader(open(data_file, encoding=encoding))
begin = int(next(csv_reader)[1])
df = pandas.read_csv(data_file, sep=",", encoding=encoding, skip_blank_lines=False, header=begin - 1, on_bad_lines="skip")
row = len(df) - margin
if float(df.iloc[row, 2]) < threshold:
row = self.find_point("backward", step, margin, threshold, "a1", data_file, "lt", df, row)
_row = self.find_point("backward", step, margin, threshold, "a2", data_file, "gt", df, row)
_row = self.find_point("backward", step, margin, threshold, "a3", data_file, "lt", df, _row)
row_end = self.find_point("backward", step, margin, threshold, "a4", data_file, "gt", df, _row)
row_middle = self.find_point("backward", step, margin, threshold, "a5", data_file, "lt", df, row_end)
row_start = self.find_point("backward", step, margin, threshold, "a6", data_file, "gt", df, row_middle)
# print(f"row_end = {row_end}")
# print(f"row_middle = {row_middle}")
# print(f"row_start = {row_start}")
return row_end-row_middle, row_middle-row_start, row_end-row_start, df
def initialization(self):
_, data_files = clibs.traversal_files(self.dir_path, self.output)
for data_file in data_files:
if not data_file.lower().endswith(".csv"):
self.logger("ERROR", "wavelogger", f"init: {data_file} 文件后缀错误,只允许 .csv 文件,需要确认!", "red", "FileTypeError")
return data_files
def preparation(self, data_file, step, margin, threshold, wb):
shtname = data_file.split("/")[-1].split(".")[0]
ws = wb.create_sheet(shtname)
low, high, cycle, df = self.get_cycle_info(data_file, step, margin, threshold)
return ws, df, low, high, cycle
def single_file_proc(self, ws, data_file, step, threshold, margin, data_length, df, cycle):
row, row_lt, row_gt, count, count_i, data = 1, 1, 1, 1, 1, {}
row_max = len(df) - margin
while row < row_max:
if count not in data.keys():
data[count] = []
value = float(df.iloc[row, 2])
if value < threshold:
row_lt = self.find_point("forward", step, margin, threshold, "c"+str(row), data_file, "lt", df, row)
start = int(row_gt + (row_lt - row_gt - data_length) / 2)
end = start + data_length
value = df.iloc[start:end, 2].astype(float).mean() + 3 * df.iloc[start:end, 2].astype(float).std()
if value > 1:
msg = f"\n"
self.logger("WARNING", "wavelogger", f"{data_file} 文件第 {count} 轮 第 {count_i} 个数据可能有问题,需人工手动确认,确认有问题可删除,无问题则保留", "purple")
data[count].append(value)
count_i += 1
else:
row_gt = self.find_point("forward", step, margin, threshold, "c"+str(row), data_file, "gt", df, row)
if row_gt - row_lt > cycle * 2:
count += 1
count_i = 1
row = max(row_gt, row_lt)
for i in range(2, 10):
ws.cell(row=1, column=i).value = f"{i-1}次测试"
ws.cell(row=i, column=1).value = f"{i-1}次精度变化"
for i in sorted(data.keys()):
row, column = 2, i + 1
for value in data[i]:
ws.cell(row=row, column=column).value = float(value)
row += 1
def execution(self, data_files):
self.logger("INFO", "wavelogger", "正在处理中......", "blue")
wb = openpyxl.Workbook()
step, margin, data_length, threshold = 5, 50, 50, 5
for data_file in data_files:
ws, df, low, high, cycle = self.preparation(data_file, step, margin, threshold, wb)
self.single_file_proc(ws, data_file, step, threshold, margin, data_length, df, cycle)
wd = "/".join(data_files[0].split("/")[:-1])
filename = wd + "/result.xlsx"
wb.save(filename)
wb.close()
def processing(self):
time_start = time.time()
clibs.running[self.idx] = 1
data_files = self.initialization()
self.execution(data_files)
self.logger("INFO", "wavelogger", "-" * 60 + "<br>全部处理完毕<br>", "purple")
time_total = time.time() - time_start
msg = f"数据处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s\n"
self.logger("INFO", "wavelogger", msg)

View File

383
codes/autotest/do_brake.py Normal file
View File

@ -0,0 +1,383 @@
import time
import os
import paramiko
import openpyxl
import pandas
import json
from PySide6.QtCore import Signal, QThread
from codes.common import clibs
class DoBrakeTest(QThread):
output = Signal(str, str)
def __init__(self, dir_path, tool, /):
super().__init__()
self.dir_path = dir_path
self.tool = tool
self.idx = 4
def logger(self, level, module, content, color="black", error="", flag="both"):
flag = "cursor" if level.upper() == "DEBUG" else "both"
clibs.logger(level, module, content, color, flag, signal=self.output)
if level.upper() == "ERROR":
raise Exception(f"{error} | {content}")
def initialization(self, data_dirs, data_files):
@clibs.handle_exception
def check_files():
msg = "初始路径下不允许有文件夹,只能存在如下五个文件,且文件为关闭状态,确认后重新运行!<br>"
msg += "1. configs.xlsx<br>2. reach33/reach66/reach100_xxxx.xlsx<br>3. xxxx.zip"
if len(data_dirs) != 0 or len(data_files) != 5:
self.logger("ERROR", "do_brake", msg, "red", "InitFileError")
config_file, reach33_file, reach66_file, reach100_file, prj_file, result_dirs = None, None, None, None, None, []
for data_file in data_files:
filename = data_file.split("/")[-1]
if filename == "configs.xlsx":
config_file = data_file
elif filename.startswith("reach33_") and filename.endswith(".xlsx"):
reach33_file = data_file
elif filename.startswith("reach66_") and filename.endswith(".xlsx"):
reach66_file = data_file
elif filename.startswith("reach100_") and filename.endswith(".xlsx"):
reach100_file = data_file
elif filename.endswith(".zip"):
prj_file = data_file
else:
self.logger("ERROR", "do_brake", msg, "red", "InitFileError")
if config_file and reach33_file and reach66_file and reach100_file and prj_file:
os.mkdir(f"{self.dir_path}/j1")
os.mkdir(f"{self.dir_path}/j2")
os.mkdir(f"{self.dir_path}/j3")
load = f"load{self.tool.removeprefix('tool')}"
for reach in ["reach33", "reach66", "reach100"]:
for speed in ["speed33", "speed66", "speed100"]:
dir_name = "_".join([reach, load, speed])
result_dirs.append(dir_name)
os.mkdir(f"{self.dir_path}/j1/{dir_name}")
os.mkdir(f"{self.dir_path}/j2/{dir_name}")
if reach == "reach100":
os.mkdir(f"{self.dir_path}/j3/{dir_name}")
return config_file, prj_file, result_dirs
else:
self.logger("ERROR", "do_brake", msg, "red", "InitFileError")
@clibs.handle_exception
def get_configs():
robot_type = None
msg_id = clibs.c_hr.execution("controller.get_params")
records = clibs.c_hr.get_from_id(msg_id)
for record in records:
if "请求发送成功" not in record[0]:
robot_type = eval(record[0])["data"]["robot_type"]
server_file = f"/home/luoshi/bin/controller/robot_cfg/{robot_type}/{robot_type}.cfg"
local_file = self.dir_path + f"/{robot_type}.cfg"
clibs.c_pd.pull_file_from_server(server_file, local_file)
try:
with open(local_file, mode="r", encoding="utf-8") as f_config:
configs = json.load(f_config)
except Exception as Err:
self.logger("ERROR", "do_brake", f"无法打开 {local_file}<br>{Err}", "red", "OpenFileError")
# 最大角速度,额定电流,减速比,额定转速
version = configs["VERSION"]
avs = configs["MOTION"]["JOINT_MAX_SPEED"]
self.logger("INFO", "do_brake", f"get_configs: 机型文件版本 {robot_type}_{version}")
self.logger("INFO", "do_brake", f"get_configs: 各关节角速度 {avs}")
return avs
self.logger("INFO", "do_brake", "正在做初始化校验和配置,这可能需要一点时间......", "green")
_config_file, _prj_file, _result_dirs = check_files()
_avs = get_configs()
self.logger("INFO", "do_brake", "数据目录合规性检查结束,未发现问题......", "green")
return _config_file, _prj_file, _result_dirs, _avs
@clibs.handle_exception
def gen_result_file(self, axis, end_time, reach, load, speed, speed_max, rounds):
d_vel, d_trq, d_stop, threshold = [], [], [], 0.95
s_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(end_time-clibs.INTERVAL*12))
e_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(end_time))
try:
clibs.lock.acquire(True)
clibs.cursor.execute(f"SELECT content FROM logs WHERE timestamp BETWEEN '{s_time}' AND '{e_time}' AND content LIKE '%diagnosis.result%' ORDER BY id ASC")
records = clibs.cursor.fetchall()
finally:
clibs.lock.release()
for record in records: # 保留最后12s的数据
data = eval(record[0])["data"]
for item in data:
d_item = reversed(item["value"])
if item.get("channel", None) == axis-1 and item.get("name", None) == "hw_joint_vel_feedback":
d_vel.extend(d_item)
elif item.get("channel", None) == axis-1 and item.get("name", None) == "device_servo_trq_feedback":
d_trq.extend(d_item)
elif item.get("channel", None) == 0 and item.get("name", None) == "device_safety_estop":
d_stop.extend(d_item)
idx = 0
for idx in range(len(d_stop)-10, 0, -1):
if d_stop[idx] == 1:
break
av_estop = abs(sum(d_vel[idx - 20:idx])/20 * clibs.RADIAN)
if av_estop / speed_max < threshold:
self.logger("WARNING", "do_brake", f"[av_estop: {av_estop:.2f} | shouldbe: {speed_max:.2f}] 处理数据时,本次触发 ESTOP 时未采集到指定百分比的最大速度,即将重试!", "#8A2BE2")
clibs.count += 1
if clibs.count < 3:
return "retry"
else:
clibs.count = 0
self.logger("WARNING", "do_brake",f"尝试三次后仍无法获取正确数据,本次数据无效,继续执行...", "red")
df1 = pandas.DataFrame.from_dict({"hw_joint_vel_feedback": d_vel})
df2 = pandas.DataFrame.from_dict({"device_servo_trq_feedback": d_trq})
df3 = pandas.DataFrame.from_dict({"device_safety_estop": d_stop})
df = pandas.concat([df1, df2, df3], axis=1)
filename = f"{self.dir_path}/j{axis}/reach{reach}_load{load}_speed{speed}/reach{reach}_load{load}_speed{speed}_{rounds}.data"
df.to_csv(filename, sep="\t", index=False)
@staticmethod
@clibs.handle_exception
def change_curve_state(stat):
if not stat:
display_pdo_params = []
else:
display_pdo_params = [{"name": name, "channel": chl} for name in ["hw_joint_vel_feedback", "device_servo_trq_feedback"] for chl in range(6)]
display_pdo_params.append({"name": "device_safety_estop", "channel": 0})
clibs.c_hr.execution("diagnosis.open", open=stat, display_open=stat)
clibs.c_hr.execution("diagnosis.set_params", display_pdo_params=display_pdo_params)
@clibs.handle_exception
def run_rl(self, config_file, prj_file, result_dirs, avs):
count, total, speed_target = 0, 63, 0
prj_name = ".".join(prj_file.split("/")[-1].split(".")[:-1])
wb = openpyxl.load_workbook(config_file, read_only=True)
ws = wb["Target"]
write_diagnosis = float(ws.cell(row=2, column=2).value)
get_init_speed = float(ws.cell(row=3, column=2).value)
single_brake = str(ws.cell(row=4, column=2).value)
pon = ws.cell(row=5, column=2).value
io_name = ws.cell(row=6, column=2).value.upper().strip()
wb.close()
msg = f"基本参数配置write_diagnosis(废弃) = {write_diagnosis}, get_init_speed = {get_init_speed}, single_brake = {single_brake}, pon = {pon}"
self.logger("INFO", "do_brake", msg)
if pon == "positive":
clibs.c_md.write_pon(1)
elif pon == "negative":
clibs.c_md.write_pon(0)
else:
self.logger("ERROR", "do_brake", "configs.xlsx 中 Target 页面 B5 单元格填写不正确,检查后重新运行...", "red", "DirectionError")
self.change_curve_state(True)
for condition in result_dirs:
reach = condition.split("_")[0].removeprefix("reach")
load = condition.split("_")[1].removeprefix("load")
speed = condition.split("_")[2].removeprefix("speed")
# for single condition test
single_axis = -1
if single_brake != "0":
total = 3
single_axis = int(single_brake.split("-")[0])
if reach != single_brake.split("-")[1] or load != single_brake.split("-")[2] or speed != single_brake.split("-")[3]:
continue
for axis in range(1, 4):
# for single condition test
if (single_axis != -1 and single_axis != axis) or (axis == 3 and reach != "100"):
continue
clibs.c_md.write_axis(axis)
self.logger("INFO", "brake-processing", "-" * 90, "purple", flag="signal")
speed_max = 0
for rounds in range(1, 4):
count += 1
_ = 3 if count % 3 == 0 else count % 3
this_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
prj_path = f"{prj_name}/_build/{prj_name}.prj"
msg = f"[{this_time} | {count}/{total}] 正在执行 {axis}{condition} 的第 {_} 次制动测试..."
self.logger("INFO", "do_brake", msg)
# 1. 触发软急停,并解除,目的是让可能正在运行着的机器停下来,切手动模式并下电
clibs.c_md.r_soft_estop(0)
clibs.c_md.r_soft_estop(1)
clibs.c_ec.setdo_value(io_name, "true")
clibs.c_md.r_reset_estop()
clibs.c_md.r_clear_alarm()
clibs.c_md.write_act(0)
while count % 3 == 1:
# 2. 修改要执行的场景
rl_cmd = ""
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=clibs.ip_addr, port=clibs.ssh_port, username=clibs.username, password=clibs.password)
if pon == "positive":
rl_cmd = f"brake_E(j{axis}_{reach}_p, j{axis}_{reach}_n, p_speed, p_tool)"
elif pon == "negative":
rl_cmd = f"brake_E(j{axis}_{reach}_n, j{axis}_{reach}_p, p_speed, p_tool)"
rl_speed = f"VelSet {speed}"
rl_tool = f"tool p_tool = {self.tool}"
cmd = "cd /home/luoshi/bin/controller/; "
cmd += f'sudo sed -i "/brake_E/d" projects/{prj_name}/_build/brake/main.mod; '
cmd += f'sudo sed -i "/DONOTDELETE/i {rl_cmd}" projects/{prj_name}/_build/brake/main.mod; '
cmd += f'sudo sed -i "/VelSet/d" projects/{prj_name}/_build/brake/main.mod; '
cmd += f'sudo sed -i "/MoveAbsJ/i {rl_speed}" projects/{prj_name}/_build/brake/main.mod; '
cmd += f'sudo sed -i "/tool p_tool/d" projects/{prj_name}/_build/brake/main.mod; '
cmd += f'sudo sed -i "/VelSet/i {rl_tool}" projects/{prj_name}/_build/brake/main.mod; '
stdin, stdout, stderr = ssh.exec_command(cmd, get_pty=True)
stdin.write(clibs.password + "\n")
stdout.read().decode() # 需要read一下才能正常执行
stderr.read().decode()
# 3. reload工程后pp2main并且自动模式和上电最后运行程序
clibs.c_hr.execution("overview.reload", prj_path=prj_path, tasks=["brake"])
clibs.c_hr.execution("rl_task.pp_to_main", tasks=["brake"])
clibs.c_hr.execution("state.switch_auto")
clibs.c_hr.execution("state.switch_motor_on")
clibs.c_hr.execution("rl_task.set_run_params", loop_mode=True, override=1.0)
clibs.c_hr.execution("rl_task.run", tasks=["brake"])
t_start = time.time()
while True:
if clibs.c_md.read_ready_to_go() == 1:
clibs.c_md.write_act(True)
break
else:
time.sleep(1)
if (time.time() - t_start) > 15:
self.logger("ERROR", "do_brake", "15s 内未收到机器人的运行信号,需要确认 RL 程序编写正确并正常执行...", "red", "ReadySignalTimeoutError")
# 4. 找出最大速度传递给RL程序最后清除相关记录
time.sleep(5) # 消除前 5s 的不稳定数据
start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
time.sleep(get_init_speed) # 指定时间后获取实际【正|负】方向的最大速度可通过configs.xlsx配置
end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
clibs.c_hr.execution("rl_task.stop", tasks=["brake"])
# 找出最大速度
@clibs.db_lock
@clibs.handle_exception
def get_speed_max():
_speed_max = 0
clibs.cursor.execute(f"SELECT content FROM logs WHERE timestamp BETWEEN '{start_time}' AND '{end_time}' AND content LIKE '%diagnosis.result%' ORDER BY id ASC")
records = clibs.cursor.fetchall()
for record in records:
data = eval(record[0])["data"]
for item in data:
if item.get("channel", None) == axis-1 and item.get("name", None) == "hw_joint_vel_feedback":
_ = clibs.RADIAN * sum(item["value"]) / len(item["value"])
if pon == "positive":
_speed_max = max(_, _speed_max)
elif pon == "negative":
_speed_max = min(_, _speed_max)
return _speed_max
speed_max = abs(get_speed_max())
speed_target = avs[axis-1] * float(speed) / 100
self.logger("INFO", "do_brake", f"axis = {axis}, direction = {pon}, max speed = {speed_max}")
if speed_max < speed_target*0.95 or speed_max > speed_target*1.05:
self.logger("WARNING", "do_brake", f"Axis: {axis}-{count} | 采集获取最大 Speed: {speed_max} | Shouldbe: {speed_target}", "indigo")
clibs.c_md.write_speed_max(speed_max)
if speed_max < 10:
clibs.c_md.r_clear_alarm()
self.logger("WARNING", "do_brake", f"未获取到正确的速度,即将重新获取...", "red")
continue
else:
break
while 1:
clibs.c_ec.setdo_value(io_name, "true")
clibs.c_md.r_reset_estop()
clibs.c_md.r_clear_alarm()
clibs.c_md.write_act(0)
# 5. 重新运行程序发送继续运动信号当速度达到最大值时通过DO触发急停
clibs.c_hr.execution("rl_task.pp_to_main", tasks=["brake"])
clibs.c_hr.execution("state.switch_auto")
clibs.c_hr.execution("state.switch_motor_on")
t_start = time.time()
while 1:
clibs.c_md.r_clear_alarm()
clibs.c_hr.execution("rl_task.run", tasks=["brake"])
time.sleep(1)
if clibs.c_md.w_program_state == 1:
break
else:
time.sleep(5)
if time.time() - t_start > 60:
self.logger("ERROR", "do_brake","60s 内程序未能正常执行,需检查...", "red", "RlProgramStartTimeout")
for i in range(16):
if clibs.c_md.read_ready_to_go() == 1:
clibs.c_md.write_act(1)
break
else:
time.sleep(1)
else:
self.logger("ERROR", "do_brake", "16s 内未收到机器人的运行信号,需要确认 RL 程序配置正确并正常执行...", "red", "ReadySignalTimeoutError")
@clibs.handle_exception
def exec_brake():
flag, start, data, record = True, time.time(), None, None
while flag:
time.sleep(0.05)
if time.time() - start > 20:
self.logger("ERROR", "do_brake", "20s 内未触发急停,需排查......", "red", "BrakeTimeoutError")
try:
clibs.lock.acquire(True)
clibs.cursor.execute(f"select content from logs where content like '%diagnosis.result%' order by id desc limit 1")
record = clibs.cursor.fetchone()
data = eval(record[0])["data"]
finally:
clibs.lock.release()
for item in data:
if item.get("channel", None) != axis-1 or item.get("name", None) != "hw_joint_vel_feedback":
continue
speed_moment = clibs.RADIAN * sum(item["value"]) / len(item["value"])
if abs(speed_moment) > speed_max - 2:
if (pon == "positive" and speed_moment > 0) or (pon == "negative" and speed_moment < 0):
clibs.c_ec.setdo_value(io_name, "false")
time.sleep(clibs.INTERVAL*2) # wait speed goes down to 0
flag = False
break
return time.time()
time.sleep(11) # 排除从其他位姿到零点位姿,再到轴极限位姿的时间
end_time = exec_brake()
# 6. 保留数据并处理输出
ret = self.gen_result_file(axis, end_time, reach, load, speed, speed_max, rounds)
if ret != "retry":
clibs.count = 0
break
else:
time.sleep(50) # why?
self.change_curve_state(False)
msg = f"<br>{self.tool.removeprefix('tool')}%负载的制动性能测试执行完毕,如需采集其他负载,须切换负载类型,并更换其他负载,重新执行"
self.logger("INFO", "do_brake", msg, "green")
@clibs.handle_exception
def processing(self):
time_start = time.time()
clibs.running[self.idx] = 1
if clibs.status["hmi"] != 1 or clibs.status["md"] != 1 or clibs.status["ec"] != 1:
self.logger("ERROR", "do_brake", "processing: 需要在网络设置中连接HMIModbus通信以及外部通信", "red", "NetworkError")
data_dirs, data_files = clibs.traversal_files(self.dir_path, self.output)
config_file, prj_file, result_dirs, avs = self.initialization(data_dirs, data_files)
clibs.c_pd.push_prj_to_server(prj_file)
self.run_rl(config_file, prj_file, result_dirs, avs)
self.logger("INFO", "do_brake", "-"*60 + "<br>全部处理完毕<br>", "purple")
time_total = time.time() - time_start
msg = f"处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s"
self.logger("INFO", "do_brake", msg)

View File

@ -0,0 +1,289 @@
import os
import threading
import time
import paramiko
import pandas
from PySide6.QtCore import Signal, QThread
from codes.common import clibs
class DoCurrentTest(QThread):
output = Signal(str, str)
def __init__(self, dir_path, tool, /):
super().__init__()
self.dir_path = dir_path
self.tool = tool
self.idx = 5
def logger(self, level, module, content, color="black", error="", flag="both"):
flag = "cursor" if level.upper() == "DEBUG" else "both"
clibs.logger(level, module, content, color, flag, signal=self.output)
if level.upper() == "ERROR":
raise Exception(f"{error} | {content}")
def initialization(self, data_dirs, data_files):
@clibs.handle_exception
def check_files():
msg = "初始路径下不允许有文件夹,初始路径下只能存在如下两个文件,且文件为关闭状态,确认后重新运行!<br>"
msg += "1. T_电机电流.xlsx<br>2. xxxx.zip"
if len(data_dirs) != 0 or len(data_files) != 2:
self.logger("ERROR", "do_current", msg, "red", "InitFileError")
prj_file, count = None, 0
for data_file in data_files:
filename = data_file.split("/")[-1]
if filename == "T_电机电流.xlsx":
count += 1
elif filename.endswith(".zip"):
count += 1
prj_file = data_file
else:
self.logger("ERROR", "do_current", msg, "red", "InitFileError")
if count != 2:
self.logger("ERROR", "do_current", msg, "red", "InitFileError")
if self.tool == "tool100":
os.mkdir(f"{self.dir_path}/single")
os.mkdir(f"{self.dir_path}/s_1")
os.mkdir(f"{self.dir_path}/s_2")
os.mkdir(f"{self.dir_path}/s_3")
elif self.tool == "inertia":
os.mkdir(f"{self.dir_path}/inertia")
else:
self.logger("ERROR", "do_current", "负载选择错误,电机电流测试只能选择 tool100/inertia 规格!", "red", "LoadSelectError")
return prj_file
@clibs.handle_exception
def get_configs():
robot_type = None
msg_id = clibs.c_hr.execution("controller.get_params")
records = clibs.c_hr.get_from_id(msg_id)
for record in records:
if "请求发送成功" not in record[0]:
robot_type = eval(record[0])["data"]["robot_type"]
server_file = f"/home/luoshi/bin/controller/robot_cfg/{robot_type}/{robot_type}.cfg"
local_file = self.dir_path + f"/{robot_type}.cfg"
clibs.c_pd.pull_file_from_server(server_file, local_file)
self.logger("INFO", "do_current", "正在做初始化校验和配置,这可能需要一点时间......", "green")
_prj_file = check_files()
get_configs()
self.logger("INFO", "do_current", "数据目录合规性检查结束,未发现问题......", "green")
return _prj_file
@clibs.handle_exception
def single_axis_proc(self, records, number):
text = "single" if number < 6 else "hold"
number = number if number < 6 else number - 6
d_vel, d_trq, d_sensor, d_trans = [], [], [], []
for record in records:
data = eval(record[0])["data"]
for item in data:
d_item = reversed(item["value"])
if item.get("channel", None) == number and item.get("name", None) == "hw_joint_vel_feedback":
d_vel.extend(d_item)
elif item.get("channel", None) == number and item.get("name", None) == "device_servo_trq_feedback":
d_trq.extend(d_item)
elif item.get("channel", None) == number and item.get("name", None) == "hw_sensor_trq_feedback":
d_sensor.extend(d_item)
elif item.get("channel", None) == number and item.get("name", None) == "hw_estimate_trans_trq_res":
d_trans.extend(d_item)
df1 = pandas.DataFrame.from_dict({"hw_joint_vel_feedback": d_vel})
df2 = pandas.DataFrame.from_dict({"device_servo_trq_feedback": d_trq})
df3 = pandas.DataFrame.from_dict({"hw_sensor_trq_feedback": d_sensor})
df4 = pandas.DataFrame.from_dict({"hw_estimate_trans_trq_res": d_trans})
df = pandas.concat([df1, df2, df3, df4], axis=1)
filename = f"{self.dir_path}/single/j{number + 1}_{text}_{time.time()}.data"
df.to_csv(filename, sep="\t", index=False)
@clibs.handle_exception
def scenario_proc(self, records, number, scenario_time):
d_vel, d_trq, d_sensor, d_trans = [[], [], [], [], [], []], [[], [], [], [], [], []], [[], [], [], [], [], []], [[], [], [], [], [], []]
for record in records:
data = eval(record[0])["data"]
for item in data:
d_item = reversed(item["value"])
for axis in range(6):
if item.get("channel", None) == axis and item.get("name", None) == "hw_joint_vel_feedback":
d_vel[axis].extend(d_item)
elif item.get("channel", None) == axis and item.get("name", None) == "device_servo_trq_feedback":
d_trq[axis].extend(d_item)
elif item.get("channel", None) == axis and item.get("name", None) == "hw_sensor_trq_feedback":
d_sensor[axis].extend(d_item)
elif item.get("channel", None) == axis and item.get("name", None) == "hw_estimate_trans_trq_res":
d_trans[axis].extend(d_item)
for axis in range(6):
df1 = pandas.DataFrame.from_dict({"hw_joint_vel_feedback": d_vel[axis]})
df2 = pandas.DataFrame.from_dict({"device_servo_trq_feedback": d_trq[axis]})
df3 = pandas.DataFrame.from_dict({"hw_sensor_trq_feedback": d_sensor[axis]})
df4 = pandas.DataFrame.from_dict({"hw_estimate_trans_trq_res": d_trans[axis]})
df = pandas.concat([df1, df2, df3, df4], axis=1)
filename = f"{self.dir_path}/s_{number-11}/j{axis+1}_s_{number-11}_{scenario_time}_{time.time()}.data"
df.to_csv(filename, sep="\t", index=False)
@clibs.handle_exception
def gen_result_file(self, number, start_time, end_time, scenario_time):
@clibs.handle_exception
def get_records():
s_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(start_time))
e_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(end_time+clibs.INTERVAL))
try:
clibs.lock.acquire(True)
clibs.cursor.execute(f"SELECT content FROM logs WHERE timestamp BETWEEN '{s_time}' AND '{e_time}' AND content LIKE '%diagnosis.result%' ORDER BY id ASC")
return clibs.cursor.fetchall()
finally:
clibs.lock.release()
if number < 12:
records = get_records()
t = threading.Thread(target=self.single_axis_proc, args=(records, number))
t.daemon = True
t.start()
elif number < 15:
records = get_records()
t = threading.Thread(target=self.scenario_proc, args=(records, number, scenario_time))
t.daemon = True
t.start()
@staticmethod
@clibs.handle_exception
def change_curve_state(stat):
curves = ["hw_joint_vel_feedback", "device_servo_trq_feedback", "hw_sensor_trq_feedback", "hw_estimate_trans_trq_res"]
display_pdo_params = [] if not stat else [{"name": curve, "channel": chl} for curve in curves for chl in range(6)]
clibs.c_hr.execution("diagnosis.open", open=stat, display_open=stat)
clibs.c_hr.execution("diagnosis.set_params", display_pdo_params=display_pdo_params)
@clibs.handle_exception
def run_rl(self, prj_file):
prj_name = ".".join(prj_file.split("/")[-1].split(".")[:-1])
c_regular = [
"scenario(0, j1_p, j1_n, p_speed, p_tool, i_tool)",
"scenario(0, j2_p, j2_n, p_speed, p_tool, i_tool)",
"scenario(0, j3_p, j3_n, p_speed, p_tool, i_tool)",
"scenario(0, j4_p, j4_n, p_speed, p_tool, i_tool)",
"scenario(0, j5_p, j5_n, p_speed, p_tool, i_tool)",
"scenario(0, j6_p, j6_n, p_speed, p_tool, i_tool)",
"scenario(4, j1_hold, j1_hold, p_speed, p_tool, i_tool)",
"scenario(4, j2_hold, j2_hold, p_speed, p_tool, i_tool)",
"scenario(4, j3_hold, j3_hold, p_speed, p_tool, i_tool)",
"scenario(4, j4_hold, j4_hold, p_speed, p_tool, i_tool)",
"scenario(4, j5_hold, j5_hold, p_speed, p_tool, i_tool)",
"scenario(4, j6_hold, j6_hold, p_speed, p_tool, i_tool)",
"scenario(1, j6_p, j6_n, p_speed, p_tool, i_tool)",
"scenario(2, j6_p, j6_n, p_speed, p_tool, i_tool)",
"scenario(3, j6_p, j6_n, p_speed, p_tool, i_tool)",
]
c_inertia = [
"scenario(5, j4_p_inertia, j4_n_inertia, p_speed, p_tool, i_tool)",
"scenario(5, j5_p_inertia, j5_n_inertia, p_speed, p_tool, i_tool)",
"scenario(5, j6_p_inertia, j6_n_inertia, p_speed, p_tool, i_tool)",
]
disc_regular = ["一轴", "二轴", "三轴", "四轴", "五轴", "六轴", "一轴保持", "二轴保持", "三轴保持", "四轴保持", "五轴保持", "六轴保持", "场景一", "场景二", "场景三"]
disc_inertia = ["四轴惯量", "五轴惯量", "六轴惯量"]
conditions, disc = [], []
if self.tool == "tool100":
conditions, disc = c_regular, disc_regular
elif self.tool == "inertia":
conditions, disc = c_inertia, disc_inertia
# 打开诊断曲线,触发软急停,并解除,目的是让可能正在运行着的机器停下来
clibs.c_md.r_soft_estop(0)
clibs.c_md.r_soft_estop(1)
clibs.c_md.r_clear_alarm()
for condition in conditions:
number = conditions.index(condition)
# for testing
# if number < 12:
# continue
self.logger("INFO", "do_current", f"正在执行{disc[number]}测试......")
# 1. 将act重置为False并修改将要执行的场景
clibs.c_md.write_act(False)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(clibs.ip_addr, clibs.ssh_port, username=clibs.username, password=clibs.password)
cmd = "cd /home/luoshi/bin/controller/; "
cmd += f'sudo sed -i "/scenario/d" projects/{prj_name}/_build/current/main.mod; '
cmd += f'sudo sed -i "/DONOTDELETE/i {condition}" projects/{prj_name}/_build/current/main.mod'
stdin, stdout, stderr = ssh.exec_command(cmd, get_pty=True)
stdin.write(clibs.password + "\n")
stdout.read().decode() # 需要read一下才能正常执行
stderr.read().decode()
# 2. reload工程后pp2main并且自动模式和上电
prj_path = f"{prj_name}/_build/{prj_name}.prj"
clibs.c_hr.execution("overview.reload", prj_path=prj_path, tasks=["current"])
clibs.c_hr.execution("rl_task.pp_to_main", tasks=["current"])
clibs.c_hr.execution("state.switch_auto")
clibs.c_hr.execution("state.switch_motor_on")
# 3. 开始运行程序
clibs.c_hr.execution("rl_task.set_run_params", loop_mode=True, override=1.0)
clibs.c_hr.execution("rl_task.run", tasks=["current"])
t_start = time.time()
while True:
if clibs.c_md.read_ready_to_go() == 1:
clibs.c_md.write_act(True)
break
else:
time.sleep(1)
if (time.time() - t_start) > 15:
self.logger("ERROR", "do_current", "15s 内未收到机器人的运行信号需要确认RL程序和工具通信是否正常执行...", "red", "ReadySignalTimeoutError")
# 4. 执行采集
time.sleep(10) # 消除前 10s 的不稳定数据
self.change_curve_state(True)
start_time = time.time()
single_time, stall_time, scenario_time = 40, 10, 0
if number < 6: # 单轴
time.sleep(single_time)
elif number < 12: # 堵转
time.sleep(stall_time)
else: # 场景
t_start = time.time()
while True:
scenario_time = float(f"{float(clibs.c_md.read_scenario_time()):.2f}")
if float(scenario_time) != 0:
self.logger("INFO", "do_current", f"场景{number - 11}的周期时间:{scenario_time}")
break
else:
time.sleep(1)
if (time.time()-t_start) > 180:
self.logger("ERROR", "do_current", f"180s 内未收到场景{number - 11}的周期时间需要确认RL程序和工具通信交互是否正常执行...", "red", "GetScenarioTimeError")
time.sleep(20)
# 5.停止程序运行,保留数据并处理输出
end_time = time.time()
clibs.c_hr.execution("rl_task.stop", tasks=["current"])
self.change_curve_state(False)
time.sleep(2) # 确保数据都入库
self.gen_result_file(number, start_time, end_time, scenario_time)
else:
if self.tool == "tool100":
self.logger("INFO", "do_current", "单轴和场景电机电流采集完毕,如需采集惯量负载,须切换负载类型,并更换惯量负载,重新执行", "green")
elif self.tool == "inertia":
self.logger("INFO", "do_current", "惯量负载电机电流采集完毕,如需采集单轴/场景/保持电机电流,须切换负载类型,并更换偏置负载,重新执行", "green")
@clibs.handle_exception
def processing(self):
time_start = time.time()
clibs.running[self.idx] = 1
if clibs.status["hmi"] != 1 or clibs.status["md"] != 1:
self.logger("ERROR", "do_current", "processing: 需要在网络设置中连接HMI以及Modbus通信", "red", "NetworkError")
data_dirs, data_files = clibs.traversal_files(self.dir_path, self.output)
prj_file = self.initialization(data_dirs, data_files)
clibs.c_pd.push_prj_to_server(prj_file)
self.run_rl(prj_file)
self.logger("INFO", "do_current", "-" * 60 + "<br>全部处理完毕<br>", "purple")
time_total = time.time() - time_start
msg = f"处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s"
self.logger("INFO", "do_current", msg)

0
codes/common/__init__.py Normal file
View File

103
codes/common/clibs.py Normal file
View File

@ -0,0 +1,103 @@
import os
import os.path
import threading
import sqlite3
def traversal_files(dir_path, signal):
# 功能:以列表的形式分别返回指定路径下的文件和文件夹,不包含子目录
# 参数:路径/信号/游标/功能编号
# 返回值:路径下的文件夹列表 路径下的文件列表
if not os.path.exists(dir_path):
logger("ERROR", "clibs", f"数据文件夹{dir_path}不存在,请确认后重试......", "red", signal=signal)
else:
dirs, files = [], []
for item in os.scandir(dir_path):
if item.is_dir():
dirs.append(item.path.replace("\\", "/"))
elif item.is_file():
files.append(item.path.replace("\\", "/"))
return dirs, files
def init_logdb():
global conn, cursor
conn = sqlite3.connect(":memory:", isolation_level=None, check_same_thread=False, cached_statements=2048)
cursor = conn.cursor()
cursor.execute("PRAGMA journal_mode=wal")
cursor.execute("PRAGMA wal_checkpoint=TRUNCATE")
cursor.execute("PRAGMA synchronous=normal")
cursor.execute("PRAGMA temp_store=memory")
cursor.execute("PRAGMA mmap_size=30000000000")
cursor.execute("PRAGMA cache_size=200000")
cursor.execute(
"""
create table if not exists logs(
id integer primary key autoincrement,
timestamp DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW', 'localtime')),
level text,
module text,
content text
)
"""
)
def db_lock(func):
def wrapper(*args, **kwargs):
try:
lock.acquire(True)
ret = func(*args, **kwargs)
finally:
lock.release()
return ret
return wrapper
@db_lock
def logger(level, module, content, color="black", flag="both", signal=""):
global cursor
if "move.monitor" in content:
return
if flag == "signal":
signal.emit(content, color)
elif flag == "cursor":
cursor.execute(f"INSERT INTO logs (level, module, content) VALUES (?, ?, ?)", (level, module, content))
elif flag == "both":
signal.emit(content, color)
cursor.execute(f"INSERT INTO logs (level, module, content) VALUES (?, ?, ?)", (level, module, content))
def handle_exception(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as err:
print(f"{func.__name__} err = {err}")
logger("DEBUG", "clibs", f"{func.__name__} err = {err}", flag="cursor")
return wrapper
log_path = f"assets/logs"
lock = threading.Lock()
running = [0, 0, 0, 0, 0, 0, 0] # 制动数据/转矩数据/激光数据/精度数据/制动自动化/转矩自动化/耐久数据采集
functions = ["制动数据处理", "转矩数据处理", "激光数据处理", "精度数据处理", "制动自动化测试", "转矩自动化测试", "耐久数据采集"]
levels = ["DEBUG", "INFO", "WARNING", "ERROR"]
ip_addr, ssh_port, socket_port, xService_port, external_port, modbus_port, upgrade_port = "", 22, 5050, 6666, 8080, 502, 4567
username, password = "luoshi", "luoshi2019"
INTERVAL, RADIAN, MAX_FRAME_SIZE, MAX_LOG_NUMBER = 1, 57.3, 1024, 10
c_md, c_hr, c_ec, c_pd, conn, cursor, search_records = None, None, None, None, None, None, None
status = {"mysql": 0, "hmi": 0, "md": 0, "ec": 0}
c_joint_vel, c_servo_trq, c_sensor_trq, c_estimate_trans_trq, c_safety_estop = 1, 2, 3, 4, 3 # 各个指标所在列
init_logdb()
# ============== ↓↓↓DEBUG CODE↓↓↓ ==============
for i in range(100):
logger("DEBUG", "clibs", 'running123', flag="cursor")
logger("INFO", "aio", 'running234', flag="cursor")
logger("WARNING", "openapi", 'running345', flag="cursor")
logger("ERROR", "brake", 'running456', flag="cursor")

2489
codes/common/openapi.py Normal file

File diff suppressed because it is too large Load Diff

View File

View File

@ -0,0 +1,293 @@
import json
import threading
import time
import pandas
import numpy
import math
import csv
from PySide6.QtCore import Signal, QThread
from codes.common import clibs
class DoFactoryTest(QThread):
output = Signal(str, str)
curve_map = {
"周期内平均转矩": ["device_servo_trq_feedback", ],
"周期内最大速度": ["hw_joint_vel_feedback", ],
}
def __init__(self, dir_path, interval, procs, /):
super().__init__()
self.dir_path = dir_path
self.interval = int(interval) if interval != "" else 300
self.procs = procs
self.idx = 6
self.curves = []
def logger(self, level, module, content, color="black", error="", flag="both"):
flag = "cursor" if level.upper() == "DEBUG" else "both"
clibs.logger(level, module, content, color, flag, signal=self.output)
if level.upper() == "ERROR":
raise Exception(f"{error} | {content}")
def initialization(self, data_dirs, data_files):
@clibs.handle_exception
def check_files():
for proc_name, is_enabled in self.procs.items():
if is_enabled:
self.curves.extend(self.curve_map[proc_name])
if len(self.curves) == 0:
self.logger("ERROR", "factory", "未查询到需要记录数据的曲线,至少选择一个!", "red", "CurveNameError")
if len(data_dirs) != 0 or len(data_files) != 1:
self.logger("ERROR", "factory", "初始路径下不允许有文件夹,且初始路径下只能存在一个工程文件 —— *.zip确认后重新运行", "red", "InitFileError")
if not data_files[0].endswith(".zip"):
self.logger("ERROR", "factory", f"{data_files[0]} 不是一个有效的工程文件,需确认!", "red", "ProjectFileError")
return data_files[0]
@clibs.handle_exception
def get_configs():
robot_type, records = None, None
msg_id = clibs.c_hr.execution("controller.get_params")
records = clibs.c_hr.get_from_id(msg_id)
for record in records:
if "请求发送成功" not in record[0]:
robot_type = eval(record[0])["data"]["robot_type"]
server_file = f"/home/luoshi/bin/controller/robot_cfg/{robot_type}/{robot_type}.cfg"
local_file = self.dir_path + f"/{robot_type}.cfg"
clibs.c_pd.pull_file_from_server(server_file, local_file)
try:
with open(local_file, mode="r", encoding="utf-8") as f_config:
configs = json.load(f_config)
except Exception as Err:
self.logger("ERROR", "factory", f"无法打开 {local_file}<br>{Err}", "red", "OpenFileError")
# 最大角速度,额定电流,减速比,额定转速
version = configs["VERSION"]
m_avs = configs["MOTION"]["JOINT_MAX_SPEED"]
self.logger("INFO", "factory", f"get_configs: 机型文件版本 {robot_type}_{version}")
self.logger("INFO", "factory", f"get_configs: 各关节角速度 {m_avs}")
return m_avs
self.logger("INFO", "factory", "正在做初始化校验和配置,这可能需要一点时间......", "green")
prj_file = check_files()
if prj_file is None:
return
avs = get_configs()
params = {
"prj_file": prj_file,
"interval": self.interval,
"avs": avs,
}
self.logger("INFO", "factory", "数据目录合规性检查结束,未发现问题......", "green")
return params
@clibs.handle_exception
def change_curve_state(self, stat):
display_pdo_params = [{"name": name, "channel": chl} for name in self.curves for chl in range(6)]
clibs.c_hr.execution("diagnosis.open", open=stat, display_open=stat)
clibs.c_hr.execution("diagnosis.set_params", display_pdo_params=display_pdo_params)
@clibs.handle_exception
def run_rl(self, params):
prj_file, interval = params["prj_file"], params["interval"]
# 1. 关闭诊断曲线,触发软急停,并解除,目的是让可能正在运行着的机器停下来,切手动模式并下电
self.change_curve_state(False)
clibs.c_md.r_soft_estop(0)
clibs.c_md.r_soft_estop(1)
clibs.c_md.r_clear_alarm()
clibs.c_md.write_act(False)
time.sleep(1) # 让曲线彻底关闭
# 2. reload工程后pp2main并且自动模式和上电
prj_name = ".".join(prj_file.split("/")[-1].split(".")[:-1])
prj_path = f"{prj_name}/_build/{prj_name}.prj"
clibs.c_hr.execution("overview.reload", prj_path=prj_path, tasks=["factory"])
clibs.c_hr.execution("rl_task.pp_to_main", tasks=["factory"])
clibs.c_hr.execution("state.switch_auto")
clibs.c_hr.execution("state.switch_motor_on")
# 3. 开始运行程序
self.logger("INFO", "factory", f"正在采集场景工程的周期大概1min左右......", "blue")
clibs.c_hr.execution("rl_task.set_run_params", loop_mode=True, override=1.0)
clibs.c_hr.execution("rl_task.run", tasks=["factory"])
t_start = time.time()
while True:
if clibs.c_md.read_ready_to_go() == 1:
clibs.c_md.write_act(True)
break
else:
if (time.time() - t_start) > 15:
self.logger("ERROR", "factory", "15s 内未收到机器人的运行信号需要确认RL程序编写正确并正常执行...", "red", "ReadySignalTimeoutError")
else:
time.sleep(clibs.INTERVAL)
# 4. 获取初始数据,周期时间,首次的各轴平均电流值,打开诊断曲线,并执行采集
time.sleep(clibs.INTERVAL*10) # 等待 RL 程序中 scenario_time 初始化
t_start = time.time()
while True:
scenario_time = float(f"{float(clibs.c_md.read_scenario_time()):.2f}")
if scenario_time != 0:
self.logger("INFO", "factory", f"耐久工程的周期时间:{scenario_time}s | 单轮次执行时间:{scenario_time+interval}~{scenario_time*2+interval}")
break
else:
time.sleep(clibs.INTERVAL)
if (time.time() - t_start) > 900:
self.logger("ERROR", "factory", f"900s内未收到耐久工程的周期时间需要确认RL程序和工具通信交互是否正常执行支持最长工程周期时间为300s......", "red", "GetScenarioTimeError")
# 6. 准备数据保存文件
for proc_name, is_enabled in self.procs.items():
if not is_enabled:
continue
with open(f"{self.dir_path}/{proc_name}.csv", mode="a+", newline="") as f_csv:
for curve in self.curve_map[proc_name]:
titles = [f"{curve}_{i}" for i in range(6)]
titles.insert(0, "time")
csv_writer = csv.writer(f_csv)
csv_writer.writerow(titles)
# 7. 开始采集
count = 0
while clibs.running[self.idx]:
this_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
next_time_1 = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()+scenario_time+interval+1))
next_time_2 = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()+scenario_time+interval+1+scenario_time))
self.logger("INFO", "factory", f"[{this_time}] 当前次数:{count:09d} | 预计下次数据更新时间:{next_time_1}~{next_time_2}", "#008B8B")
count += 1
# 固定间隔,更新一次数据,打开曲线,获取周期内电流,关闭曲线
time.sleep(interval)
while True:
capture_start = clibs.c_md.read_capture_start()
if capture_start == 1:
break
else:
time.sleep(clibs.INTERVAL/10)
self.change_curve_state(True)
time.sleep(scenario_time)
end_time = time.time()
start_time = end_time - scenario_time
self.change_curve_state(False)
# 保留数据并处理输出
self.gen_results(params, start_time, end_time)
else:
self.change_curve_state(False)
self.logger("INFO", "factory", "后台数据清零完成,现在可以重新运行其他程序。", "green")
@clibs.handle_exception
def gen_results(self, params, start_time, end_time):
s_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(start_time))
e_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(end_time))
try:
clibs.lock.acquire(True)
clibs.cursor.execute(f"SELECT content FROM logs WHERE timestamp BETWEEN '{s_time}' AND '{e_time}' AND content LIKE '%diagnosis.result%' ORDER BY id ASC")
records = clibs.cursor.fetchall()
finally:
clibs.lock.release()
self.data_proc(records, params)
@clibs.handle_exception
def data_proc(self, records, params):
for proc_name, is_enabled in self.procs.items():
if not is_enabled:
continue
if proc_name == "周期内平均转矩":
# get_avg_trq(records, params, w2t)
t = threading.Thread(target=self.get_avg_trq, args=(records, params, proc_name))
t.daemon = True
t.start()
elif proc_name == "周期内最大速度":
# get_joint_max_vel(records, params, w2t)
t = threading.Thread(target=self.get_joint_max_vel, args=(records, params, proc_name))
t.daemon = True
t.start()
@clibs.handle_exception
def get_avg_trq(self, records, params, proc_name):
d_trq, results = [[], [], [], [], [], []], [time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))]
for record in records:
data = eval(record[0])["data"]
for item in data:
d_item = reversed(item["value"])
for axis in range(6):
if item.get("channel", None) == axis and item.get("name", None) == "device_servo_trq_feedback":
d_trq[axis].extend(d_item)
for axis in range(6):
df = pandas.DataFrame.from_dict({"device_servo_trq_feedback": d_trq[axis]})
_ = math.sqrt(numpy.square(df[df.columns[0]].values * 1.27 / 1000).sum() / len(df))
results.append(_)
path = "/".join(params["prj_file"].split("/")[:-1])
with open(f"{path}/{proc_name}.csv", mode="a+", newline="") as f_csv:
csv_writer = csv.writer(f_csv)
csv_writer.writerow(results)
@clibs.handle_exception
def get_joint_max_vel(self, records, params, proc_name):
d_trq, results = [[], [], [], [], [], []], [time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))]
for record in records:
data = eval(record[0])["data"]
for item in data:
d_item = reversed(item["value"])
for axis in range(6):
if item.get("channel", None) == axis and item.get("name", None) == "hw_joint_vel_feedback":
d_trq[axis].extend(d_item)
for axis in range(6):
df = pandas.DataFrame.from_dict({"hw_joint_vel_feedback": d_trq[axis]})
_ = df.max().iloc[0]
results.append(_)
path = "/".join(params["prj_file"].split("/")[:-1])
with open(f"{path}/{proc_name}.csv", mode="a+", newline="") as f_csv:
csv_writer = csv.writer(f_csv)
csv_writer.writerow(results)
@staticmethod
def detect_db_size():
@clibs.db_lock
@clibs.handle_exception
def release_memory():
line_number = 20000
leftover = 4000 # 200s
clibs.cursor.execute("SELECT COUNT(id) FROM logs")
len_records = clibs.cursor.fetchone()[0]
if len_records > line_number:
del_num = len_records - leftover + 1
clibs.cursor.execute(f"DELETE FROM logs WHERE id < {del_num}")
clibs.cursor.execute(f"UPDATE logs SET id=(id-{del_num - 1}) WHERE id > {del_num - 1}")
clibs.cursor.execute(f"UPDATE sqlite_sequence SET seq = {leftover + 1} WHERE name = 'logs' ")
clibs.cursor.execute("VACUUM")
while True:
release_memory()
time.sleep(clibs.INTERVAL*10)
@clibs.handle_exception
def processing(self):
time_start = time.time()
clibs.running[self.idx] = 1
if clibs.status["hmi"] != 1 or clibs.status["md"] != 1:
self.logger("ERROR", "factory", "processing: 需要在网络设置中连接HMI以及Modbus通信", "red", "NetworkError")
t = threading.Thread(target=self.detect_db_size)
t.daemon = True
t.start()
data_dirs, data_files = clibs.traversal_files(self.dir_path, self.output)
params = self.initialization(data_dirs, data_files)
clibs.c_pd.push_prj_to_server(params["prj_file"])
self.run_rl(params)
self.logger("INFO", "factory", "-"*60 + "<br>全部处理完毕<br>", "purple")
time_total = time.time() - time_start
msg = f"处理时间:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s"
self.logger("INFO", "factory", msg)

0
codes/ui/__init__.py Normal file
View File

982
codes/ui/main_window.py Normal file
View File

@ -0,0 +1,982 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'main.ui'
##
## Created by: Qt User Interface Compiler version 6.8.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QFormLayout,
QFrame, QHBoxLayout, QHeaderView, QLabel,
QLineEdit, QMainWindow, QPlainTextEdit, QPushButton,
QScrollArea, QSizePolicy, QSpacerItem, QStackedWidget,
QStatusBar, QTabWidget, QTreeWidget, QTreeWidgetItem,
QVBoxLayout, QWidget)
class Ui_MainWindow(QMainWindow):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
MainWindow.setEnabled(True)
MainWindow.resize(1004, 560)
sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
MainWindow.setSizePolicy(sizePolicy)
MainWindow.setMinimumSize(QSize(1000, 550))
font = QFont()
font.setFamilies([u"Consolas"])
font.setPointSize(14)
MainWindow.setFont(font)
icon = QIcon()
icon.addFile(u"../assets/media/icon.ico", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
MainWindow.setWindowIcon(icon)
MainWindow.setStyleSheet(u"background-color: rgb(233, 233, 233);")
MainWindow.setDocumentMode(False)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
self.formLayout = QFormLayout(self.centralwidget)
self.formLayout.setObjectName(u"formLayout")
self.vl_1_left = QVBoxLayout()
self.vl_1_left.setObjectName(u"vl_1_left")
self.label = QLabel(self.centralwidget)
self.label.setObjectName(u"label")
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
self.label.setSizePolicy(sizePolicy1)
self.label.setMinimumSize(QSize(200, 100))
self.label.setMaximumSize(QSize(240, 120))
font1 = QFont()
font1.setFamilies([u"Segoe Print"])
font1.setPointSize(24)
font1.setBold(True)
self.label.setFont(font1)
self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.label.setMargin(0)
self.vl_1_left.addWidget(self.label, 0, Qt.AlignmentFlag.AlignHCenter|Qt.AlignmentFlag.AlignVCenter)
self.btn_start = QPushButton(self.centralwidget)
self.btn_start.setObjectName(u"btn_start")
sizePolicy1.setHeightForWidth(self.btn_start.sizePolicy().hasHeightForWidth())
self.btn_start.setSizePolicy(sizePolicy1)
self.btn_start.setMinimumSize(QSize(150, 36))
self.btn_start.setMaximumSize(QSize(180, 45))
font2 = QFont()
font2.setFamilies([u"Consolas"])
font2.setPointSize(14)
font2.setBold(True)
self.btn_start.setFont(font2)
self.btn_start.setFlat(False)
self.vl_1_left.addWidget(self.btn_start, 0, Qt.AlignmentFlag.AlignHCenter|Qt.AlignmentFlag.AlignVCenter)
self.btn_stop = QPushButton(self.centralwidget)
self.btn_stop.setObjectName(u"btn_stop")
sizePolicy1.setHeightForWidth(self.btn_stop.sizePolicy().hasHeightForWidth())
self.btn_stop.setSizePolicy(sizePolicy1)
self.btn_stop.setMinimumSize(QSize(150, 36))
self.btn_stop.setMaximumSize(QSize(180, 45))
self.btn_stop.setFont(font2)
self.btn_stop.setFlat(False)
self.vl_1_left.addWidget(self.btn_stop, 0, Qt.AlignmentFlag.AlignHCenter|Qt.AlignmentFlag.AlignVCenter)
self.btn_reset = QPushButton(self.centralwidget)
self.btn_reset.setObjectName(u"btn_reset")
sizePolicy1.setHeightForWidth(self.btn_reset.sizePolicy().hasHeightForWidth())
self.btn_reset.setSizePolicy(sizePolicy1)
self.btn_reset.setMinimumSize(QSize(150, 36))
self.btn_reset.setMaximumSize(QSize(180, 45))
self.btn_reset.setFont(font2)
self.btn_reset.setFlat(False)
self.vl_1_left.addWidget(self.btn_reset, 0, Qt.AlignmentFlag.AlignHCenter|Qt.AlignmentFlag.AlignVCenter)
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.vl_1_left.addItem(self.verticalSpacer)
self.vl_1_left.setStretch(0, 4)
self.vl_1_left.setStretch(1, 1)
self.vl_1_left.setStretch(2, 1)
self.vl_1_left.setStretch(3, 1)
self.vl_1_left.setStretch(4, 10)
self.formLayout.setLayout(0, QFormLayout.LabelRole, self.vl_1_left)
self.vl_1_right = QVBoxLayout()
self.vl_1_right.setObjectName(u"vl_1_right")
self.tw_funcs = QTabWidget(self.centralwidget)
self.tw_funcs.setObjectName(u"tw_funcs")
sizePolicy.setHeightForWidth(self.tw_funcs.sizePolicy().hasHeightForWidth())
self.tw_funcs.setSizePolicy(sizePolicy)
self.tw_funcs.setMinimumSize(QSize(0, 0))
font3 = QFont()
font3.setPointSize(14)
font3.setBold(True)
self.tw_funcs.setFont(font3)
self.tw_funcs.setElideMode(Qt.TextElideMode.ElideNone)
self.tw_funcs.setUsesScrollButtons(True)
self.tw_funcs.setDocumentMode(True)
self.tw_funcs.setTabsClosable(False)
self.tw_funcs.setTabBarAutoHide(False)
self.tab_data = QWidget()
self.tab_data.setObjectName(u"tab_data")
self.verticalLayout = QVBoxLayout(self.tab_data)
self.verticalLayout.setObjectName(u"verticalLayout")
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.cb_data_func = QComboBox(self.tab_data)
self.cb_data_func.addItem("")
self.cb_data_func.addItem("")
self.cb_data_func.addItem("")
self.cb_data_func.addItem("")
self.cb_data_func.setObjectName(u"cb_data_func")
self.cb_data_func.setMinimumSize(QSize(100, 0))
font4 = QFont()
font4.setFamilies([u"Consolas"])
font4.setPointSize(12)
self.cb_data_func.setFont(font4)
self.horizontalLayout.addWidget(self.cb_data_func)
self.cb_data_current = QComboBox(self.tab_data)
self.cb_data_current.addItem("")
self.cb_data_current.addItem("")
self.cb_data_current.addItem("")
self.cb_data_current.setObjectName(u"cb_data_current")
self.cb_data_current.setMinimumSize(QSize(100, 0))
self.cb_data_current.setFont(font4)
self.horizontalLayout.addWidget(self.cb_data_current)
self.label_4 = QLabel(self.tab_data)
self.label_4.setObjectName(u"label_4")
self.label_4.setFont(font4)
self.label_4.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter)
self.horizontalLayout.addWidget(self.label_4)
self.le_data_path = QLineEdit(self.tab_data)
self.le_data_path.setObjectName(u"le_data_path")
self.le_data_path.setFont(font4)
self.le_data_path.setAlignment(Qt.AlignmentFlag.AlignLeading|Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
self.horizontalLayout.addWidget(self.le_data_path)
self.btn_data_open = QPushButton(self.tab_data)
self.btn_data_open.setObjectName(u"btn_data_open")
self.btn_data_open.setMaximumSize(QSize(30, 16777215))
self.btn_data_open.setFont(font4)
self.horizontalLayout.addWidget(self.btn_data_open)
self.horizontalLayout.setStretch(0, 1)
self.horizontalLayout.setStretch(1, 1)
self.horizontalLayout.setStretch(2, 1)
self.horizontalLayout.setStretch(3, 10)
self.horizontalLayout.setStretch(4, 1)
self.verticalLayout.addLayout(self.horizontalLayout)
self.verticalSpacer_2 = QSpacerItem(20, 161, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.verticalLayout.addItem(self.verticalSpacer_2)
self.tw_funcs.addTab(self.tab_data, "")
self.tab_unit = QWidget()
self.tab_unit.setObjectName(u"tab_unit")
self.verticalLayout_2 = QVBoxLayout(self.tab_unit)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.hl_2_unit1 = QHBoxLayout()
self.hl_2_unit1.setObjectName(u"hl_2_unit1")
self.cb_unit_func = QComboBox(self.tab_unit)
self.cb_unit_func.addItem("")
self.cb_unit_func.addItem("")
self.cb_unit_func.setObjectName(u"cb_unit_func")
self.cb_unit_func.setMinimumSize(QSize(100, 0))
self.cb_unit_func.setFont(font4)
self.hl_2_unit1.addWidget(self.cb_unit_func)
self.cb_unit_tool = QComboBox(self.tab_unit)
self.cb_unit_tool.addItem("")
self.cb_unit_tool.addItem("")
self.cb_unit_tool.addItem("")
self.cb_unit_tool.addItem("")
self.cb_unit_tool.setObjectName(u"cb_unit_tool")
self.cb_unit_tool.setMinimumSize(QSize(100, 0))
self.cb_unit_tool.setFont(font4)
self.hl_2_unit1.addWidget(self.cb_unit_tool)
self.label_6 = QLabel(self.tab_unit)
self.label_6.setObjectName(u"label_6")
sizePolicy1.setHeightForWidth(self.label_6.sizePolicy().hasHeightForWidth())
self.label_6.setSizePolicy(sizePolicy1)
self.label_6.setFont(font4)
self.label_6.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter)
self.hl_2_unit1.addWidget(self.label_6)
self.le_unit_path = QLineEdit(self.tab_unit)
self.le_unit_path.setObjectName(u"le_unit_path")
self.le_unit_path.setFont(font4)
self.hl_2_unit1.addWidget(self.le_unit_path)
self.btn_unit_open = QPushButton(self.tab_unit)
self.btn_unit_open.setObjectName(u"btn_unit_open")
self.btn_unit_open.setMaximumSize(QSize(30, 16777215))
self.btn_unit_open.setFont(font4)
self.hl_2_unit1.addWidget(self.btn_unit_open)
self.hl_2_unit1.setStretch(0, 1)
self.hl_2_unit1.setStretch(1, 1)
self.hl_2_unit1.setStretch(2, 1)
self.hl_2_unit1.setStretch(3, 10)
self.hl_2_unit1.setStretch(4, 1)
self.verticalLayout_2.addLayout(self.hl_2_unit1)
self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.verticalLayout_2.addItem(self.verticalSpacer_3)
self.tw_funcs.addTab(self.tab_unit, "")
self.tab_durable = QWidget()
self.tab_durable.setObjectName(u"tab_durable")
self.horizontalLayout_11 = QHBoxLayout(self.tab_durable)
self.horizontalLayout_11.setObjectName(u"horizontalLayout_11")
self.horizontalLayout_10 = QHBoxLayout()
self.horizontalLayout_10.setObjectName(u"horizontalLayout_10")
self.verticalLayout_9 = QVBoxLayout()
self.verticalLayout_9.setObjectName(u"verticalLayout_9")
self.frame = QFrame(self.tab_durable)
self.frame.setObjectName(u"frame")
self.frame.setMinimumSize(QSize(200, 0))
self.frame.setMaximumSize(QSize(300, 16777215))
self.frame.setFrameShape(QFrame.Shape.StyledPanel)
self.frame.setFrameShadow(QFrame.Shadow.Raised)
self.verticalLayout_8 = QVBoxLayout(self.frame)
self.verticalLayout_8.setObjectName(u"verticalLayout_8")
self.verticalLayout_7 = QVBoxLayout()
self.verticalLayout_7.setObjectName(u"verticalLayout_7")
self.label_11 = QLabel(self.frame)
self.label_11.setObjectName(u"label_11")
sizePolicy1.setHeightForWidth(self.label_11.sizePolicy().hasHeightForWidth())
self.label_11.setSizePolicy(sizePolicy1)
self.label_11.setFont(font2)
self.label_11.setAlignment(Qt.AlignmentFlag.AlignLeading|Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
self.verticalLayout_7.addWidget(self.label_11)
self.sa_durable = QScrollArea(self.frame)
self.sa_durable.setObjectName(u"sa_durable")
self.sa_durable.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents")
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 212, 78))
self.horizontalLayout_9 = QHBoxLayout(self.scrollAreaWidgetContents)
self.horizontalLayout_9.setObjectName(u"horizontalLayout_9")
self.verticalLayout_5 = QVBoxLayout()
self.verticalLayout_5.setObjectName(u"verticalLayout_5")
self.cb_1 = QCheckBox(self.scrollAreaWidgetContents)
self.cb_1.setObjectName(u"cb_1")
self.cb_1.setFont(font4)
self.verticalLayout_5.addWidget(self.cb_1)
self.cb_2 = QCheckBox(self.scrollAreaWidgetContents)
self.cb_2.setObjectName(u"cb_2")
self.cb_2.setFont(font4)
self.verticalLayout_5.addWidget(self.cb_2)
self.verticalSpacer_5 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.verticalLayout_5.addItem(self.verticalSpacer_5)
self.horizontalLayout_9.addLayout(self.verticalLayout_5)
self.sa_durable.setWidget(self.scrollAreaWidgetContents)
self.verticalLayout_7.addWidget(self.sa_durable)
self.verticalLayout_8.addLayout(self.verticalLayout_7)
self.verticalLayout_9.addWidget(self.frame)
self.horizontalLayout_10.addLayout(self.verticalLayout_9)
self.verticalLayout_6 = QVBoxLayout()
self.verticalLayout_6.setObjectName(u"verticalLayout_6")
self.horizontalLayout_6 = QHBoxLayout()
self.horizontalLayout_6.setObjectName(u"horizontalLayout_6")
self.label_8 = QLabel(self.tab_durable)
self.label_8.setObjectName(u"label_8")
sizePolicy1.setHeightForWidth(self.label_8.sizePolicy().hasHeightForWidth())
self.label_8.setSizePolicy(sizePolicy1)
self.label_8.setFont(font4)
self.label_8.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.horizontalLayout_6.addWidget(self.label_8)
self.le_durable_path = QLineEdit(self.tab_durable)
self.le_durable_path.setObjectName(u"le_durable_path")
self.le_durable_path.setFont(font4)
self.horizontalLayout_6.addWidget(self.le_durable_path)
self.btn_durable_open = QPushButton(self.tab_durable)
self.btn_durable_open.setObjectName(u"btn_durable_open")
self.btn_durable_open.setMaximumSize(QSize(30, 16777215))
self.btn_durable_open.setFont(font4)
self.horizontalLayout_6.addWidget(self.btn_durable_open)
self.verticalLayout_6.addLayout(self.horizontalLayout_6)
self.horizontalLayout_7 = QHBoxLayout()
self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
self.label_9 = QLabel(self.tab_durable)
self.label_9.setObjectName(u"label_9")
sizePolicy1.setHeightForWidth(self.label_9.sizePolicy().hasHeightForWidth())
self.label_9.setSizePolicy(sizePolicy1)
self.label_9.setFont(font4)
self.label_9.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.horizontalLayout_7.addWidget(self.label_9)
self.le_durable_interval = QLineEdit(self.tab_durable)
self.le_durable_interval.setObjectName(u"le_durable_interval")
self.le_durable_interval.setFont(font4)
self.le_durable_interval.setInputMethodHints(Qt.InputMethodHint.ImhNone)
self.horizontalLayout_7.addWidget(self.le_durable_interval)
self.label_10 = QLabel(self.tab_durable)
self.label_10.setObjectName(u"label_10")
sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.label_10.sizePolicy().hasHeightForWidth())
self.label_10.setSizePolicy(sizePolicy2)
self.label_10.setMinimumSize(QSize(30, 0))
self.horizontalLayout_7.addWidget(self.label_10)
self.verticalLayout_6.addLayout(self.horizontalLayout_7)
self.horizontalLayout_8 = QHBoxLayout()
self.horizontalLayout_8.setObjectName(u"horizontalLayout_8")
self.cb_durable_total = QCheckBox(self.tab_durable)
self.cb_durable_total.setObjectName(u"cb_durable_total")
font5 = QFont()
font5.setFamilies([u"Consolas"])
font5.setPointSize(12)
font5.setBold(True)
self.cb_durable_total.setFont(font5)
self.horizontalLayout_8.addWidget(self.cb_durable_total)
self.btn_draw = QPushButton(self.tab_durable)
self.btn_draw.setObjectName(u"btn_draw")
self.btn_draw.setFont(font5)
self.horizontalLayout_8.addWidget(self.btn_draw)
self.label_3 = QLabel(self.tab_durable)
self.label_3.setObjectName(u"label_3")
self.horizontalLayout_8.addWidget(self.label_3)
self.horizontalLayout_8.setStretch(0, 2)
self.horizontalLayout_8.setStretch(2, 8)
self.verticalLayout_6.addLayout(self.horizontalLayout_8)
self.verticalSpacer_4 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.verticalLayout_6.addItem(self.verticalSpacer_4)
self.horizontalLayout_10.addLayout(self.verticalLayout_6)
self.horizontalLayout_10.setStretch(0, 1)
self.horizontalLayout_10.setStretch(1, 2)
self.horizontalLayout_11.addLayout(self.horizontalLayout_10)
self.tw_funcs.addTab(self.tab_durable, "")
self.tab_network = QWidget()
self.tab_network.setObjectName(u"tab_network")
self.horizontalLayout_13 = QHBoxLayout(self.tab_network)
self.horizontalLayout_13.setObjectName(u"horizontalLayout_13")
self.horizontalLayout_12 = QHBoxLayout()
self.horizontalLayout_12.setObjectName(u"horizontalLayout_12")
self.sw_network = QStackedWidget(self.tab_network)
self.sw_network.setObjectName(u"sw_network")
self.page = QWidget()
self.page.setObjectName(u"page")
self.horizontalLayout_14 = QHBoxLayout(self.page)
self.horizontalLayout_14.setObjectName(u"horizontalLayout_14")
self.verticalLayout_10 = QVBoxLayout()
self.verticalLayout_10.setObjectName(u"verticalLayout_10")
self.horizontalLayout_3 = QHBoxLayout()
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
self.label_2 = QLabel(self.page)
self.label_2.setObjectName(u"label_2")
sizePolicy1.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth())
self.label_2.setSizePolicy(sizePolicy1)
self.label_2.setMinimumSize(QSize(70, 0))
self.label_2.setFont(font5)
self.label_2.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.horizontalLayout_3.addWidget(self.label_2)
self.le_hmi_ip = QLineEdit(self.page)
self.le_hmi_ip.setObjectName(u"le_hmi_ip")
self.le_hmi_ip.setMinimumSize(QSize(150, 0))
self.le_hmi_ip.setFont(font4)
self.horizontalLayout_3.addWidget(self.le_hmi_ip)
self.btn_hmi_conn = QPushButton(self.page)
self.btn_hmi_conn.setObjectName(u"btn_hmi_conn")
self.btn_hmi_conn.setFont(font5)
self.horizontalLayout_3.addWidget(self.btn_hmi_conn)
self.label_5 = QLabel(self.page)
self.label_5.setObjectName(u"label_5")
self.label_5.setFont(font4)
self.horizontalLayout_3.addWidget(self.label_5)
self.cb_hmi_cmd = QComboBox(self.page)
self.cb_hmi_cmd.addItem("")
self.cb_hmi_cmd.addItem("")
self.cb_hmi_cmd.addItem("")
self.cb_hmi_cmd.addItem("")
self.cb_hmi_cmd.addItem("")
self.cb_hmi_cmd.setObjectName(u"cb_hmi_cmd")
self.cb_hmi_cmd.setMinimumSize(QSize(240, 0))
self.cb_hmi_cmd.setFont(font4)
self.horizontalLayout_3.addWidget(self.cb_hmi_cmd)
self.btn_hmi_send = QPushButton(self.page)
self.btn_hmi_send.setObjectName(u"btn_hmi_send")
self.btn_hmi_send.setFont(font5)
self.horizontalLayout_3.addWidget(self.btn_hmi_send)
self.horizontalLayout_3.setStretch(0, 1)
self.horizontalLayout_3.setStretch(1, 4)
self.horizontalLayout_3.setStretch(2, 1)
self.horizontalLayout_3.setStretch(3, 4)
self.horizontalLayout_3.setStretch(4, 8)
self.horizontalLayout_3.setStretch(5, 1)
self.verticalLayout_10.addLayout(self.horizontalLayout_3)
self.horizontalLayout_5 = QHBoxLayout()
self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
self.pte_hmi_send = QPlainTextEdit(self.page)
self.pte_hmi_send.setObjectName(u"pte_hmi_send")
self.horizontalLayout_5.addWidget(self.pte_hmi_send)
self.pte_him_recv = QPlainTextEdit(self.page)
self.pte_him_recv.setObjectName(u"pte_him_recv")
self.horizontalLayout_5.addWidget(self.pte_him_recv)
self.verticalLayout_10.addLayout(self.horizontalLayout_5)
self.horizontalLayout_14.addLayout(self.verticalLayout_10)
self.sw_network.addWidget(self.page)
self.page_2 = QWidget()
self.page_2.setObjectName(u"page_2")
self.horizontalLayout_17 = QHBoxLayout(self.page_2)
self.horizontalLayout_17.setObjectName(u"horizontalLayout_17")
self.verticalLayout_11 = QVBoxLayout()
self.verticalLayout_11.setObjectName(u"verticalLayout_11")
self.horizontalLayout_15 = QHBoxLayout()
self.horizontalLayout_15.setObjectName(u"horizontalLayout_15")
self.label_7 = QLabel(self.page_2)
self.label_7.setObjectName(u"label_7")
sizePolicy1.setHeightForWidth(self.label_7.sizePolicy().hasHeightForWidth())
self.label_7.setSizePolicy(sizePolicy1)
self.label_7.setMinimumSize(QSize(70, 0))
self.label_7.setFont(font5)
self.label_7.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.horizontalLayout_15.addWidget(self.label_7)
self.le_md_port = QLineEdit(self.page_2)
self.le_md_port.setObjectName(u"le_md_port")
self.le_md_port.setMinimumSize(QSize(150, 0))
self.le_md_port.setFont(font4)
self.horizontalLayout_15.addWidget(self.le_md_port)
self.btn_md_conn = QPushButton(self.page_2)
self.btn_md_conn.setObjectName(u"btn_md_conn")
self.btn_md_conn.setFont(font5)
self.horizontalLayout_15.addWidget(self.btn_md_conn)
self.label_12 = QLabel(self.page_2)
self.label_12.setObjectName(u"label_12")
self.label_12.setFont(font4)
self.horizontalLayout_15.addWidget(self.label_12)
self.cb_md_cmd = QComboBox(self.page_2)
self.cb_md_cmd.addItem("")
self.cb_md_cmd.addItem("")
self.cb_md_cmd.addItem("")
self.cb_md_cmd.addItem("")
self.cb_md_cmd.addItem("")
self.cb_md_cmd.addItem("")
self.cb_md_cmd.addItem("")
self.cb_md_cmd.addItem("")
self.cb_md_cmd.setObjectName(u"cb_md_cmd")
self.cb_md_cmd.setMinimumSize(QSize(240, 0))
self.cb_md_cmd.setFont(font4)
self.horizontalLayout_15.addWidget(self.cb_md_cmd)
self.btn_md_send = QPushButton(self.page_2)
self.btn_md_send.setObjectName(u"btn_md_send")
self.btn_md_send.setFont(font5)
self.horizontalLayout_15.addWidget(self.btn_md_send)
self.horizontalLayout_15.setStretch(0, 1)
self.horizontalLayout_15.setStretch(1, 4)
self.horizontalLayout_15.setStretch(2, 1)
self.horizontalLayout_15.setStretch(3, 4)
self.horizontalLayout_15.setStretch(4, 8)
self.horizontalLayout_15.setStretch(5, 1)
self.verticalLayout_11.addLayout(self.horizontalLayout_15)
self.horizontalLayout_16 = QHBoxLayout()
self.horizontalLayout_16.setObjectName(u"horizontalLayout_16")
self.pte_md_send = QPlainTextEdit(self.page_2)
self.pte_md_send.setObjectName(u"pte_md_send")
self.horizontalLayout_16.addWidget(self.pte_md_send)
self.pte_md_recv = QPlainTextEdit(self.page_2)
self.pte_md_recv.setObjectName(u"pte_md_recv")
self.horizontalLayout_16.addWidget(self.pte_md_recv)
self.verticalLayout_11.addLayout(self.horizontalLayout_16)
self.horizontalLayout_17.addLayout(self.verticalLayout_11)
self.sw_network.addWidget(self.page_2)
self.page_3 = QWidget()
self.page_3.setObjectName(u"page_3")
self.horizontalLayout_26 = QHBoxLayout(self.page_3)
self.horizontalLayout_26.setObjectName(u"horizontalLayout_26")
self.verticalLayout_14 = QVBoxLayout()
self.verticalLayout_14.setObjectName(u"verticalLayout_14")
self.horizontalLayout_24 = QHBoxLayout()
self.horizontalLayout_24.setObjectName(u"horizontalLayout_24")
self.label_17 = QLabel(self.page_3)
self.label_17.setObjectName(u"label_17")
sizePolicy1.setHeightForWidth(self.label_17.sizePolicy().hasHeightForWidth())
self.label_17.setSizePolicy(sizePolicy1)
self.label_17.setMinimumSize(QSize(70, 0))
self.label_17.setFont(font5)
self.label_17.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.horizontalLayout_24.addWidget(self.label_17)
self.le_ec_port = QLineEdit(self.page_3)
self.le_ec_port.setObjectName(u"le_ec_port")
self.le_ec_port.setMinimumSize(QSize(150, 0))
self.le_ec_port.setFont(font4)
self.horizontalLayout_24.addWidget(self.le_ec_port)
self.btn_ec_conn = QPushButton(self.page_3)
self.btn_ec_conn.setObjectName(u"btn_ec_conn")
self.btn_ec_conn.setFont(font5)
self.horizontalLayout_24.addWidget(self.btn_ec_conn)
self.label_18 = QLabel(self.page_3)
self.label_18.setObjectName(u"label_18")
self.label_18.setFont(font4)
self.horizontalLayout_24.addWidget(self.label_18)
self.cb_ec_cmd = QComboBox(self.page_3)
self.cb_ec_cmd.addItem("")
self.cb_ec_cmd.addItem("")
self.cb_ec_cmd.addItem("")
self.cb_ec_cmd.addItem("")
self.cb_ec_cmd.addItem("")
self.cb_ec_cmd.addItem("")
self.cb_ec_cmd.addItem("")
self.cb_ec_cmd.addItem("")
self.cb_ec_cmd.setObjectName(u"cb_ec_cmd")
self.cb_ec_cmd.setMinimumSize(QSize(240, 0))
self.cb_ec_cmd.setFont(font4)
self.horizontalLayout_24.addWidget(self.cb_ec_cmd)
self.btn_ec_send = QPushButton(self.page_3)
self.btn_ec_send.setObjectName(u"btn_ec_send")
self.btn_ec_send.setFont(font5)
self.horizontalLayout_24.addWidget(self.btn_ec_send)
self.horizontalLayout_24.setStretch(0, 1)
self.horizontalLayout_24.setStretch(1, 4)
self.horizontalLayout_24.setStretch(2, 1)
self.horizontalLayout_24.setStretch(3, 4)
self.horizontalLayout_24.setStretch(4, 8)
self.horizontalLayout_24.setStretch(5, 1)
self.verticalLayout_14.addLayout(self.horizontalLayout_24)
self.horizontalLayout_25 = QHBoxLayout()
self.horizontalLayout_25.setObjectName(u"horizontalLayout_25")
self.pte_ec_send = QPlainTextEdit(self.page_3)
self.pte_ec_send.setObjectName(u"pte_ec_send")
self.horizontalLayout_25.addWidget(self.pte_ec_send)
self.pte_ec_recv = QPlainTextEdit(self.page_3)
self.pte_ec_recv.setObjectName(u"pte_ec_recv")
self.horizontalLayout_25.addWidget(self.pte_ec_recv)
self.verticalLayout_14.addLayout(self.horizontalLayout_25)
self.horizontalLayout_26.addLayout(self.verticalLayout_14)
self.sw_network.addWidget(self.page_3)
self.horizontalLayout_12.addWidget(self.sw_network)
self.verticalLayout_4 = QVBoxLayout()
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
self.pushButton = QPushButton(self.tab_network)
self.pushButton.setObjectName(u"pushButton")
self.pushButton.setFont(font5)
self.verticalLayout_4.addWidget(self.pushButton)
self.pushButton_2 = QPushButton(self.tab_network)
self.pushButton_2.setObjectName(u"pushButton_2")
self.pushButton_2.setFont(font5)
self.verticalLayout_4.addWidget(self.pushButton_2)
self.pushButton_3 = QPushButton(self.tab_network)
self.pushButton_3.setObjectName(u"pushButton_3")
self.pushButton_3.setFont(font5)
self.verticalLayout_4.addWidget(self.pushButton_3)
self.horizontalLayout_12.addLayout(self.verticalLayout_4)
self.horizontalLayout_12.setStretch(0, 11)
self.horizontalLayout_12.setStretch(1, 1)
self.horizontalLayout_13.addLayout(self.horizontalLayout_12)
self.tw_funcs.addTab(self.tab_network, "")
self.vl_1_right.addWidget(self.tw_funcs)
self.tw_docs = QTabWidget(self.centralwidget)
self.tw_docs.setObjectName(u"tw_docs")
self.tw_docs.setFont(font3)
self.tw_docs.setElideMode(Qt.TextElideMode.ElideNone)
self.tw_docs.setDocumentMode(True)
self.tab_output = QWidget()
self.tab_output.setObjectName(u"tab_output")
self.horizontalLayout_4 = QHBoxLayout(self.tab_output)
self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
self.pte_output = QPlainTextEdit(self.tab_output)
self.pte_output.setObjectName(u"pte_output")
self.pte_output.setFont(font4)
self.pte_output.setLineWrapMode(QPlainTextEdit.LineWrapMode.WidgetWidth)
self.pte_output.setReadOnly(True)
self.horizontalLayout_4.addWidget(self.pte_output)
self.tw_docs.addTab(self.tab_output, "")
self.tab_log = QWidget()
self.tab_log.setObjectName(u"tab_log")
self.verticalLayout_3 = QVBoxLayout(self.tab_log)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.treew_log = QTreeWidget(self.tab_log)
self.treew_log.setObjectName(u"treew_log")
self.treew_log.setFont(font4)
self.treew_log.header().setVisible(True)
self.verticalLayout_3.addWidget(self.treew_log)
self.horizontalLayout_2 = QHBoxLayout()
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.label_page = QLabel(self.tab_log)
self.label_page.setObjectName(u"label_page")
self.label_page.setMinimumSize(QSize(100, 0))
font6 = QFont()
font6.setFamilies([u"Consolas"])
font6.setPointSize(10)
font6.setBold(True)
self.label_page.setFont(font6)
self.label_page.setStyleSheet(u"background-color: rgb(222, 222, 222);")
self.label_page.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.horizontalLayout_2.addWidget(self.label_page)
self.btn_docs_previous = QPushButton(self.tab_log)
self.btn_docs_previous.setObjectName(u"btn_docs_previous")
self.btn_docs_previous.setFont(font4)
self.horizontalLayout_2.addWidget(self.btn_docs_previous)
self.btn_docs_realtime = QPushButton(self.tab_log)
self.btn_docs_realtime.setObjectName(u"btn_docs_realtime")
self.btn_docs_realtime.setFont(font4)
self.horizontalLayout_2.addWidget(self.btn_docs_realtime)
self.btn_docs_next = QPushButton(self.tab_log)
self.btn_docs_next.setObjectName(u"btn_docs_next")
self.btn_docs_next.setFont(font4)
self.horizontalLayout_2.addWidget(self.btn_docs_next)
self.label_13 = QLabel(self.tab_log)
self.label_13.setObjectName(u"label_13")
self.horizontalLayout_2.addWidget(self.label_13)
self.btn_docs_search = QPushButton(self.tab_log)
self.btn_docs_search.setObjectName(u"btn_docs_search")
self.btn_docs_search.setFont(font4)
self.horizontalLayout_2.addWidget(self.btn_docs_search)
self.le_docs_search = QLineEdit(self.tab_log)
self.le_docs_search.setObjectName(u"le_docs_search")
self.le_docs_search.setFont(font4)
self.horizontalLayout_2.addWidget(self.le_docs_search)
self.horizontalLayout_2.setStretch(0, 1)
self.horizontalLayout_2.setStretch(1, 1)
self.horizontalLayout_2.setStretch(2, 1)
self.horizontalLayout_2.setStretch(3, 1)
self.horizontalLayout_2.setStretch(4, 3)
self.horizontalLayout_2.setStretch(5, 1)
self.horizontalLayout_2.setStretch(6, 10)
self.verticalLayout_3.addLayout(self.horizontalLayout_2)
self.tw_docs.addTab(self.tab_log, "")
self.vl_1_right.addWidget(self.tw_docs)
self.vl_1_right.setStretch(0, 1)
self.vl_1_right.setStretch(1, 3)
self.formLayout.setLayout(0, QFormLayout.FieldRole, self.vl_1_right)
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QStatusBar(MainWindow)
self.statusbar.setObjectName(u"statusbar")
sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
sizePolicy3.setHorizontalStretch(0)
sizePolicy3.setVerticalStretch(0)
sizePolicy3.setHeightForWidth(self.statusbar.sizePolicy().hasHeightForWidth())
self.statusbar.setSizePolicy(sizePolicy3)
self.statusbar.setMinimumSize(QSize(0, 27))
self.statusbar.setStyleSheet(u"background-color: rgb(200, 200, 200);")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.btn_start.clicked.connect(MainWindow.prog_start)
self.btn_stop.clicked.connect(MainWindow.prog_stop)
self.btn_reset.clicked.connect(MainWindow.prog_reset)
self.btn_durable_open.clicked.connect(MainWindow.file_browser)
self.btn_draw.clicked.connect(MainWindow.curve_draw)
self.cb_durable_total.checkStateChanged.connect(MainWindow.durable_cb_change)
self.btn_unit_open.clicked.connect(MainWindow.file_browser)
self.btn_data_open.clicked.connect(MainWindow.file_browser)
self.btn_docs_previous.clicked.connect(MainWindow.pre_page)
self.btn_docs_realtime.clicked.connect(MainWindow.realtime_page)
self.btn_docs_next.clicked.connect(MainWindow.next_page)
self.btn_docs_search.clicked.connect(MainWindow.search_keyword)
self.le_docs_search.returnPressed.connect(MainWindow.search_keyword)
self.cb_hmi_cmd.currentTextChanged.connect(MainWindow.hmi_cb_change)
self.btn_hmi_send.clicked.connect(MainWindow.hmi_send)
self.pushButton.clicked.connect(MainWindow.hmi_page)
self.pushButton_2.clicked.connect(MainWindow.md_page)
self.pushButton_3.clicked.connect(MainWindow.ec_page)
self.cb_md_cmd.currentTextChanged.connect(MainWindow.md_cb_change)
self.btn_md_send.clicked.connect(MainWindow.md_send)
self.btn_ec_send.clicked.connect(MainWindow.ec_send)
self.btn_hmi_conn.clicked.connect(MainWindow.hmi_conn)
self.btn_md_conn.clicked.connect(MainWindow.md_conn)
self.btn_ec_conn.clicked.connect(MainWindow.ec_conn)
self.le_durable_interval.editingFinished.connect(MainWindow.check_interval)
self.cb_ec_cmd.currentTextChanged.connect(MainWindow.ec_cb_change)
self.le_hmi_ip.returnPressed.connect(MainWindow.hmi_conn)
self.tw_docs.currentChanged.connect(MainWindow.switch_log_tab)
self.treew_log.itemDoubleClicked.connect(MainWindow.show_item_content)
self.tw_funcs.setCurrentIndex(0)
self.sw_network.setCurrentIndex(0)
self.tw_docs.setCurrentIndex(0)
QMetaObject.connectSlotsByName(MainWindow)
# setupUi
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Rokae AIO", None))
self.label.setText(QCoreApplication.translate("MainWindow", u"Rokae AIO", None))
self.btn_start.setText(QCoreApplication.translate("MainWindow", u"\u5f00\u59cb\u6267\u884c", None))
self.btn_stop.setText(QCoreApplication.translate("MainWindow", u"\u505c\u6b62\u6267\u884c", None))
self.btn_reset.setText(QCoreApplication.translate("MainWindow", u"\u72b6\u6001\u91cd\u7f6e", None))
self.cb_data_func.setItemText(0, QCoreApplication.translate("MainWindow", u"\u5236\u52a8", None))
self.cb_data_func.setItemText(1, QCoreApplication.translate("MainWindow", u"\u8f6c\u77e9", None))
self.cb_data_func.setItemText(2, QCoreApplication.translate("MainWindow", u"\u6fc0\u5149", None))
self.cb_data_func.setItemText(3, QCoreApplication.translate("MainWindow", u"\u7cbe\u5ea6", None))
self.cb_data_current.setItemText(0, QCoreApplication.translate("MainWindow", u"\u5468\u671f", None))
self.cb_data_current.setItemText(1, QCoreApplication.translate("MainWindow", u"\u6700\u5927\u503c", None))
self.cb_data_current.setItemText(2, QCoreApplication.translate("MainWindow", u"\u5e73\u5747\u503c", None))
self.label_4.setText(QCoreApplication.translate("MainWindow", u"\u8def\u5f84", None))
self.btn_data_open.setText(QCoreApplication.translate("MainWindow", u"...", None))
self.tw_funcs.setTabText(self.tw_funcs.indexOf(self.tab_data), QCoreApplication.translate("MainWindow", u"\u6570\u636e\u5904\u7406", None))
self.cb_unit_func.setItemText(0, QCoreApplication.translate("MainWindow", u"\u5236\u52a8", None))
self.cb_unit_func.setItemText(1, QCoreApplication.translate("MainWindow", u"\u8f6c\u77e9", None))
self.cb_unit_tool.setItemText(0, QCoreApplication.translate("MainWindow", u"tool33", None))
self.cb_unit_tool.setItemText(1, QCoreApplication.translate("MainWindow", u"tool66", None))
self.cb_unit_tool.setItemText(2, QCoreApplication.translate("MainWindow", u"tool100", None))
self.cb_unit_tool.setItemText(3, QCoreApplication.translate("MainWindow", u"inertia", None))
self.label_6.setText(QCoreApplication.translate("MainWindow", u"\u8def\u5f84", None))
self.btn_unit_open.setText(QCoreApplication.translate("MainWindow", u"...", None))
self.tw_funcs.setTabText(self.tw_funcs.indexOf(self.tab_unit), QCoreApplication.translate("MainWindow", u"\u6574\u673a\u6d4b\u8bd5", None))
self.label_11.setText(QCoreApplication.translate("MainWindow", u"\u9009\u62e9\u6307\u6807", None))
self.cb_1.setText(QCoreApplication.translate("MainWindow", u"\u5468\u671f\u5185\u5e73\u5747\u8f6c\u77e9", None))
self.cb_2.setText(QCoreApplication.translate("MainWindow", u"\u5468\u671f\u5185\u6700\u5927\u901f\u5ea6", None))
self.label_8.setText(QCoreApplication.translate("MainWindow", u"\u8def\u5f84", None))
self.btn_durable_open.setText(QCoreApplication.translate("MainWindow", u"...", None))
self.label_9.setText(QCoreApplication.translate("MainWindow", u"\u95f4\u9694", None))
#if QT_CONFIG(whatsthis)
self.le_durable_interval.setWhatsThis("")
#endif // QT_CONFIG(whatsthis)
self.le_durable_interval.setPlaceholderText(QCoreApplication.translate("MainWindow", u"\u6bcf\u6b21\u6570\u636e\u91c7\u96c6\u7684\u65f6\u95f4\u95f4\u9694\uff0c\u9ed8\u8ba4(\u6700\u5c0f)300s", None))
self.label_10.setText("")
self.cb_durable_total.setText(QCoreApplication.translate("MainWindow", u"\u5168\u90e8\u6253\u5f00/\u5173\u95ed", None))
self.btn_draw.setText(QCoreApplication.translate("MainWindow", u"\u7ed8\u56fe", None))
self.label_3.setText("")
self.tw_funcs.setTabText(self.tw_funcs.indexOf(self.tab_durable), QCoreApplication.translate("MainWindow", u"\u8010\u4e45\u91c7\u96c6", None))
self.label_2.setText(QCoreApplication.translate("MainWindow", u"HMI IP", None))
self.le_hmi_ip.setText(QCoreApplication.translate("MainWindow", u"192.168.0.160", None))
self.btn_hmi_conn.setText(QCoreApplication.translate("MainWindow", u"\u8fde\u63a5", None))
self.label_5.setText("")
self.cb_hmi_cmd.setItemText(0, QCoreApplication.translate("MainWindow", u"controller.heart", None))
self.cb_hmi_cmd.setItemText(1, QCoreApplication.translate("MainWindow", u"diagnosis.open", None))
self.cb_hmi_cmd.setItemText(2, QCoreApplication.translate("MainWindow", u"diagnosis.get_params", None))
self.cb_hmi_cmd.setItemText(3, QCoreApplication.translate("MainWindow", u"device.get_params", None))
self.cb_hmi_cmd.setItemText(4, QCoreApplication.translate("MainWindow", u"safety_area_data", None))
self.btn_hmi_send.setText(QCoreApplication.translate("MainWindow", u"\u53d1\u9001", None))
self.label_7.setText(QCoreApplication.translate("MainWindow", u"MD Port", None))
self.le_md_port.setText(QCoreApplication.translate("MainWindow", u"502", None))
self.btn_md_conn.setText(QCoreApplication.translate("MainWindow", u"\u8fde\u63a5", None))
self.label_12.setText("")
self.cb_md_cmd.setItemText(0, QCoreApplication.translate("MainWindow", u"ctrl_motor_on", None))
self.cb_md_cmd.setItemText(1, QCoreApplication.translate("MainWindow", u"ctrl_motor_off", None))
self.cb_md_cmd.setItemText(2, QCoreApplication.translate("MainWindow", u"ctrl_soft_estop", None))
self.cb_md_cmd.setItemText(3, QCoreApplication.translate("MainWindow", u"ctrl_estop_reset_clear_alarm", None))
self.cb_md_cmd.setItemText(4, QCoreApplication.translate("MainWindow", u"sta_motor", None))
self.cb_md_cmd.setItemText(5, QCoreApplication.translate("MainWindow", u"sta_soft_estop", None))
self.cb_md_cmd.setItemText(6, QCoreApplication.translate("MainWindow", u"sta_estop", None))
self.cb_md_cmd.setItemText(7, QCoreApplication.translate("MainWindow", u"sta_alarm", None))
self.btn_md_send.setText(QCoreApplication.translate("MainWindow", u"\u53d1\u9001", None))
self.label_17.setText(QCoreApplication.translate("MainWindow", u"EC Port", None))
self.le_ec_port.setText(QCoreApplication.translate("MainWindow", u"8080", None))
self.btn_ec_conn.setText(QCoreApplication.translate("MainWindow", u"\u8fde\u63a5", None))
self.label_18.setText("")
self.cb_ec_cmd.setItemText(0, QCoreApplication.translate("MainWindow", u"motor_on", None))
self.cb_ec_cmd.setItemText(1, QCoreApplication.translate("MainWindow", u"motor_off", None))
self.cb_ec_cmd.setItemText(2, QCoreApplication.translate("MainWindow", u"set_do", None))
self.cb_ec_cmd.setItemText(3, QCoreApplication.translate("MainWindow", u"switch_mode_auto", None))
self.cb_ec_cmd.setItemText(4, QCoreApplication.translate("MainWindow", u"switch_mode_manual", None))
self.cb_ec_cmd.setItemText(5, QCoreApplication.translate("MainWindow", u"operating_mode", None))
self.cb_ec_cmd.setItemText(6, QCoreApplication.translate("MainWindow", u"motor_on_state", None))
self.cb_ec_cmd.setItemText(7, QCoreApplication.translate("MainWindow", u"robot_running_state", None))
self.btn_ec_send.setText(QCoreApplication.translate("MainWindow", u"\u53d1\u9001", None))
self.pushButton.setText(QCoreApplication.translate("MainWindow", u"HMI", None))
self.pushButton_2.setText(QCoreApplication.translate("MainWindow", u"Modbus", None))
self.pushButton_3.setText(QCoreApplication.translate("MainWindow", u"EC", None))
self.tw_funcs.setTabText(self.tw_funcs.indexOf(self.tab_network), QCoreApplication.translate("MainWindow", u"\u7f51\u7edc\u8bbe\u7f6e", None))
self.tw_docs.setTabText(self.tw_docs.indexOf(self.tab_output), QCoreApplication.translate("MainWindow", u"\u8f93\u51fa", None))
___qtreewidgetitem = self.treew_log.headerItem()
___qtreewidgetitem.setText(4, QCoreApplication.translate("MainWindow", u"Content", None));
___qtreewidgetitem.setText(3, QCoreApplication.translate("MainWindow", u"Module", None));
___qtreewidgetitem.setText(2, QCoreApplication.translate("MainWindow", u"Level", None));
___qtreewidgetitem.setText(1, QCoreApplication.translate("MainWindow", u"Timestamp", None));
___qtreewidgetitem.setText(0, QCoreApplication.translate("MainWindow", u"ID", None));
self.label_page.setText(QCoreApplication.translate("MainWindow", u"0/0", None))
self.btn_docs_previous.setText(QCoreApplication.translate("MainWindow", u"\u4e0a\u4e00\u9875", None))
self.btn_docs_realtime.setText(QCoreApplication.translate("MainWindow", u"\u5b9e\u65f6", None))
self.btn_docs_next.setText(QCoreApplication.translate("MainWindow", u"\u4e0b\u4e00\u9875", None))
self.label_13.setText("")
self.btn_docs_search.setText(QCoreApplication.translate("MainWindow", u"\u67e5\u627e", None))
self.le_docs_search.setPlaceholderText(QCoreApplication.translate("MainWindow", u"[id/level/module] \u67e5\u627e\u5185\u5bb9", None))
self.tw_docs.setTabText(self.tw_docs.indexOf(self.tab_log), QCoreApplication.translate("MainWindow", u"\u65e5\u5fd7", None))
# retranslateUi