完成了电流->转矩的转变,基本完成

This commit is contained in:
gitea 2025-01-22 16:14:19 +08:00
parent 7f815ac63e
commit 92bdf133f2
48 changed files with 662 additions and 870240 deletions

View File

@ -6,8 +6,8 @@ VSVersionInfo(
ffi=FixedFileInfo( ffi=FixedFileInfo(
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
# Set not needed items to zero 0. # Set not needed items to zero 0.
filevers=(0, 3, 0, 0), filevers=(0, 3, 1, 0),
prodvers=(0, 3, 0, 0), prodvers=(0, 3, 1, 0),
# Contains a bitmask that specifies the valid bits 'flags'r # Contains a bitmask that specifies the valid bits 'flags'r
mask=0x3f, mask=0x3f,
# Contains a bitmask that specifies the Boolean attributes of the file. # Contains a bitmask that specifies the Boolean attributes of the file.
@ -31,12 +31,12 @@ VSVersionInfo(
'040904b0', '040904b0',
[StringStruct('CompanyName', 'Rokae - https://www.rokae.com/'), [StringStruct('CompanyName', 'Rokae - https://www.rokae.com/'),
StringStruct('FileDescription', 'All in one automatic toolbox'), StringStruct('FileDescription', 'All in one automatic toolbox'),
StringStruct('FileVersion', '0.3.0.0 (2025-01-17)'), StringStruct('FileVersion', '0.3.1.0 (2025-01-22)'),
StringStruct('InternalName', 'AIO.exe'), StringStruct('InternalName', 'AIO.exe'),
StringStruct('LegalCopyright', '© 2024-2025 Manford Fan'), StringStruct('LegalCopyright', '© 2024-2025 Manford Fan'),
StringStruct('OriginalFilename', 'AIO.exe'), StringStruct('OriginalFilename', 'AIO.exe'),
StringStruct('ProductName', 'AIO'), StringStruct('ProductName', 'AIO'),
StringStruct('ProductVersion', '0.3.0.0 (2025-01-17)')]) StringStruct('ProductVersion', '0.3.1.0 (2025-01-22)')])
]), ]),
VarFileInfo([VarStruct('Translation', [1033, 1200])]) VarFileInfo([VarStruct('Translation', [1033, 1200])])
] ]

View File

@ -1 +1 @@
0.3.0.0@01/17/2025 0.3.1.0@01/22/2025

View File

@ -13,6 +13,7 @@ import os
from common import clibs, openapi from common import clibs, openapi
from data_process import current, brake, iso, wavelogger from data_process import current, brake, iso, wavelogger
from automatic_test import do_current, do_brake from automatic_test import do_current, do_brake
from durable_docs import factory_test, create_plot
import threading import threading
import re import re
@ -38,6 +39,10 @@ class App:
self.entry_tips_v = None self.entry_tips_v = None
self.server_vers = None self.server_vers = None
self.tv_cols = {1: ["ID", 1], 2: ["time", 160], 3: ["level", 25], 4: ["module", 30], 5: ["content", 700]} self.tv_cols = {1: ["ID", 1], 2: ["time", 160], 3: ["level", 25], 4: ["module", 30], 5: ["content", 700]}
self.chk_box_v = ctk.BooleanVar()
self.chk_box_v.set(False)
self.entry_path_ddv = ctk.StringVar()
self.entry_path_ddv.set("数据文件夹路径")
# ======================================================================== # ========================================================================
self.frame_left = ctk.CTkFrame(self.root, width=80, corner_radius=0, fg_color="#E9E9E9") self.frame_left = ctk.CTkFrame(self.root, width=80, corner_radius=0, fg_color="#E9E9E9")
@ -46,6 +51,7 @@ class App:
self.tabview_top._segmented_button.configure(font=ctk.CTkFont(family="Consolas", size=18, weight="bold")) self.tabview_top._segmented_button.configure(font=ctk.CTkFont(family="Consolas", size=18, weight="bold"))
self.tabview_top.add("数据处理") self.tabview_top.add("数据处理")
self.tabview_top.add("自动测试") self.tabview_top.add("自动测试")
self.tabview_top.add("耐久记录")
# ------- # -------
self.tabview_bottom = ctk.CTkTabview(self.root, fg_color="#E9E9E9", segmented_button_selected_color="#008B8B", segmented_button_selected_hover_color="#2F4F4F", border_width=2, anchor="w", command=self.__switch_tab_bottom) self.tabview_bottom = ctk.CTkTabview(self.root, fg_color="#E9E9E9", segmented_button_selected_color="#008B8B", segmented_button_selected_hover_color="#2F4F4F", border_width=2, anchor="w", command=self.__switch_tab_bottom)
self.tabview_bottom._segmented_button.configure(font=ctk.CTkFont(family="Consolas", size=18, weight="bold")) self.tabview_bottom._segmented_button.configure(font=ctk.CTkFont(family="Consolas", size=18, weight="bold"))
@ -58,23 +64,25 @@ class App:
self.btn_start = ctk.CTkButton(self.frame_left, text="开始运行", font=self.f_normal, fg_color="#4F4F4F", command=lambda: self.__thread_it(self.__program_start)) self.btn_start = ctk.CTkButton(self.frame_left, text="开始运行", font=self.f_normal, fg_color="#4F4F4F", command=lambda: self.__thread_it(self.__program_start))
self.btn_reset_state = ctk.CTkButton(self.frame_left, text="重置状态", font=self.f_normal, fg_color="#4F4F4F", command=self.__reset_state) self.btn_reset_state = ctk.CTkButton(self.frame_left, text="重置状态", font=self.f_normal, fg_color="#4F4F4F", command=self.__reset_state)
# ======================================================================== # ========================================================================
self.om_main_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=160, dynamic_resizing=False, values=["brake", "current", "iso", "wavelogger"], font=self.f_common, text_color="#3C3C3C", button_color="#7B6B5B", fg_color="#8D8D8D", command=self.__main_switch_dp) self.om_main_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=160, dynamic_resizing=False, values=["brake", "current", "iso", "wavelogger"], font=self.f_common, text_color="#3C3C3C", button_color="#7B6B5B", fg_color="#8D8D8D", button_hover_color="#708090", command=self.__main_switch_dp)
self.om_sub_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=160, dynamic_resizing=False, values=["cycle", "max", "avg"], font=self.f_common, text_color="#3C3C3C", button_color="#7B6B5B", fg_color="#8D8D8D") self.om_sub_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=160, dynamic_resizing=False, values=["cycle", "max", "avg"], font=self.f_common, text_color="#3C3C3C", button_color="#7B6B5B", fg_color="#8D8D8D", button_hover_color="#708090")
self.label_path_dp = ctk.CTkLabel(self.tabview_top.tab("数据处理"), width=40, anchor="e", text="Path", font=self.f_common) self.label_path_dp = ctk.CTkLabel(self.tabview_top.tab("数据处理"), width=40, anchor="e", text="Path", font=self.f_common)
self.entry_path_dp = ctk.CTkEntry(self.tabview_top.tab("数据处理"), width=80, state="disabled", textvariable=self.entry_path_dpv, font=self.f_entry, text_color="#818181") self.entry_path_dp = ctk.CTkEntry(self.tabview_top.tab("数据处理"), width=80, state="disabled", textvariable=self.entry_path_dpv, font=self.f_entry, text_color="#818181")
self.label_vel_dp = ctk.CTkLabel(self.tabview_top.tab("数据处理"), width=40, anchor="e", text="Vel", font=self.f_common) self.label_vel_dp = ctk.CTkLabel(self.tabview_top.tab("数据处理"), width=40, anchor="e", text="Vel", font=self.f_common)
self.om_vel_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=80, values=["1", "2", "3", "4", "5"], font=self.f_common, button_color="#7B6B5B", fg_color="#8D8D8D", text_color_disabled="#808000") self.om_vel_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=80, values=["1", "2", "3", "4", "5"], font=self.f_common, button_color="#7B6B5B", fg_color="#8D8D8D", button_hover_color="#708090", text_color_disabled="#808000")
self.label_trq_dp = ctk.CTkLabel(self.tabview_top.tab("数据处理"), width=40, anchor="e", text="Trq", font=self.f_common) self.label_trq_dp = ctk.CTkLabel(self.tabview_top.tab("数据处理"), width=40, anchor="e", text="Trq", font=self.f_common)
self.om_trq_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=80, values=["1", "2", "3", "4", "5"], font=self.f_common, button_color="#7B6B5B", fg_color="#8D8D8D", text_color_disabled="#808000") self.om_trq_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=80, values=["1", "2", "3", "4", "5"], font=self.f_common, button_color="#7B6B5B", fg_color="#8D8D8D", button_hover_color="#708090", text_color_disabled="#808000")
self.label_trqh_dp = ctk.CTkLabel(self.tabview_top.tab("数据处理"), width=40, anchor="e", text="TrqH", font=self.f_common) self.label_trqh_dp = ctk.CTkLabel(self.tabview_top.tab("数据处理"), width=40, anchor="e", text="TrqH", font=self.f_common)
self.om_trqh_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=80, values=["1", "2", "3", "4", "5"], font=self.f_common, button_color="#7B6B5B", fg_color="#8D8D8D", text_color_disabled="#808000") self.om_trqh_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=80, values=["1", "2", "3", "4", "5"], font=self.f_common, button_color="#7B6B5B", fg_color="#8D8D8D", button_hover_color="#708090", text_color_disabled="#808000")
self.label_sensor_dp = ctk.CTkLabel(self.tabview_top.tab("数据处理"), width=60, anchor="e", text="Sensor", font=self.f_common) self.label_sensor_dp = ctk.CTkLabel(self.tabview_top.tab("数据处理"), width=60, anchor="e", text="Sensor", font=self.f_common)
self.om_sensor_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=80, values=["1", "2", "3", "4", "5"], font=self.f_common, button_color="#7B6B5B", fg_color="#8D8D8D", text_color_disabled="#808000") self.om_sensor_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=80, values=["1", "2", "3", "4", "5"], font=self.f_common, button_color="#7B6B5B", fg_color="#8D8D8D", button_hover_color="#708090", text_color_disabled="#808000")
self.label_estop_dp = ctk.CTkLabel(self.tabview_top.tab("数据处理"), width=60, anchor="e", text="Estop", font=self.f_common) self.label_estop_dp = ctk.CTkLabel(self.tabview_top.tab("数据处理"), width=60, anchor="e", text="Estop", font=self.f_common)
self.om_estop_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=80, values=["1", "2", "3", "4", "5"], font=self.f_common, button_color="#7B6B5B", fg_color="#8D8D8D", text_color_disabled="#808000") self.om_estop_dp = ctk.CTkOptionMenu(self.tabview_top.tab("数据处理"), width=80, values=["1", "2", "3", "4", "5"], font=self.f_common, button_color="#7B6B5B", fg_color="#8D8D8D", button_hover_color="#708090", text_color_disabled="#808000")
self.popupmenu_path_dp = tk.Menu(self.entry_path_dp, tearoff=False)
self.popupmenu_path_dp.add_command(label="复制", accelerator="Ctrl+C", font=self.f_treeview, command=lambda: self.__copy_path(self.entry_path_dp))
# ======================================================================== # ========================================================================
self.om_main_at = ctk.CTkOptionMenu(self.tabview_top.tab("自动测试"), width=160, dynamic_resizing=False, values=["brake", "current"], font=self.f_common, text_color="#3C3C3C", button_color="#7B6B5B", fg_color="#8D8D8D") self.om_main_at = ctk.CTkOptionMenu(self.tabview_top.tab("自动测试"), width=160, dynamic_resizing=False, values=["brake", "current"], font=self.f_common, text_color="#3C3C3C", button_color="#7B6B5B", fg_color="#8D8D8D", button_hover_color="#708090")
self.om_sub_at = ctk.CTkOptionMenu(self.tabview_top.tab("自动测试"), width=160, dynamic_resizing=False, values=["tool33", "tool66", "tool100", "inertia"], font=self.f_common, text_color="#3C3C3C", button_color="#7B6B5B", fg_color="#8D8D8D") self.om_sub_at = ctk.CTkOptionMenu(self.tabview_top.tab("自动测试"), width=160, dynamic_resizing=False, values=["tool33", "tool66", "tool100", "inertia"], font=self.f_common, text_color="#3C3C3C", button_color="#7B6B5B", fg_color="#8D8D8D", button_hover_color="#708090")
self.label_ip_at = ctk.CTkLabel(self.tabview_top.tab("自动测试"), anchor="e", text="IP", font=self.f_common) self.label_ip_at = ctk.CTkLabel(self.tabview_top.tab("自动测试"), anchor="e", text="IP", font=self.f_common)
self.entry_ip_at = ctk.CTkEntry(self.tabview_top.tab("自动测试"), width=160, textvariable=self.entry_ip_atv, font=self.f_entry, text_color="#818181") self.entry_ip_at = ctk.CTkEntry(self.tabview_top.tab("自动测试"), width=160, textvariable=self.entry_ip_atv, font=self.f_entry, text_color="#818181")
self.btn_conn = ctk.CTkButton(self.tabview_top.tab("自动测试"), text="连接", width=60, font=self.f_segbtn, fg_color="#979DA2", corner_radius=0, cursor="hand2") self.btn_conn = ctk.CTkButton(self.tabview_top.tab("自动测试"), text="连接", width=60, font=self.f_segbtn, fg_color="#979DA2", corner_radius=0, cursor="hand2")
@ -89,16 +97,30 @@ class App:
self.popupmenu_ip.add_command(label="剪切", accelerator="Ctrl+X", font=self.f_treeview, command=lambda: self.__cut(self.entry_ip_at)) self.popupmenu_ip.add_command(label="剪切", accelerator="Ctrl+X", font=self.f_treeview, command=lambda: self.__cut(self.entry_ip_at))
self.popupmenu_ip.add_command(label="粘贴", accelerator="Ctrl+V", font=self.f_treeview, command=lambda: self.__paste(self.entry_ip_at)) self.popupmenu_ip.add_command(label="粘贴", accelerator="Ctrl+V", font=self.f_treeview, command=lambda: self.__paste(self.entry_ip_at))
self.popupmenu_ip.add_command(label="全选", accelerator="Ctrl+A", font=self.f_treeview, command=lambda: self.__select(self.entry_ip_at)) self.popupmenu_ip.add_command(label="全选", accelerator="Ctrl+A", font=self.f_treeview, command=lambda: self.__select(self.entry_ip_at))
self.popupmenu_path_at = tk.Menu(self.entry_path_at, tearoff=False)
self.popupmenu_path_at.add_command(label="复制", accelerator="Ctrl+C", font=self.f_treeview, command=lambda: self.__copy_path(self.entry_path_at))
# ========================================================================
self.scrollable_frame = ctk.CTkScrollableFrame(self.tabview_top.tab("耐久记录"), width=360, height=60, label_text="选择曲线", label_font=self.f_segbtn)
self.switch_1 = ctk.CTkSwitch(self.scrollable_frame, text="hw_joint_vel_feedback", font=self.f_treeview, onvalue=True, offvalue=False)
self.switch_2 = ctk.CTkSwitch(self.scrollable_frame, text="device_servo_trq_feedback", font=self.f_treeview, onvalue=True, offvalue=False)
self.label_path_dd = ctk.CTkLabel(self.tabview_top.tab("耐久记录"), width=80, anchor="e", text="指定路径", font=self.f_segbtn_l)
self.entry_path_dd = ctk.CTkEntry(self.tabview_top.tab("耐久记录"), width=80, state="disabled", textvariable=self.entry_path_ddv, font=self.f_entry, text_color="#818181")
self.label_interval = ctk.CTkLabel(self.tabview_top.tab("耐久记录"), text="间隔时间", width=80, font=self.f_segbtn_l)
self.entry_interval = ctk.CTkEntry(self.tabview_top.tab("耐久记录"), placeholder_text="采样间隔时间,默认(最小)300s", font=self.f_entry)
self.btn_plot = ctk.CTkButton(self.tabview_top.tab("耐久记录"), text="绘图", width=80, font=self.f_segbtn, fg_color="#979DA2", corner_radius=0, command=self.__create_plot)
self.chk_box = ctk.CTkCheckBox(self.tabview_top.tab("耐久记录"), text="全部打开/关闭", checkbox_width=15, checkbox_height=15, corner_radius=0, onvalue=True, offvalue=False, variable=self.chk_box_v, width=20, font=self.f_segbtn_l, command=self.__all_or_none)
self.popupmenu_path_dd = tk.Menu(self.entry_path_dd, tearoff=False)
self.popupmenu_path_dd.add_command(label="复制", accelerator="Ctrl+C", font=self.f_treeview, command=lambda: self.__copy_path(self.entry_path_dd))
# ======================================================================== # ========================================================================
self.text_output = ctk.CTkTextbox(self.tabview_bottom.tab("输出"), height=10, corner_radius=0, wrap="none", font=self.f_text) self.text_output = ctk.CTkTextbox(self.tabview_bottom.tab("输出"), height=10, corner_radius=0, wrap="none", font=self.f_text)
# ======================================================================== # ========================================================================
self.label_logs = ctk.CTkLabel(self.tabview_bottom.tab("日志"), width=80, font=self.f_pager, textvariable=self.label_pages_logs, text_color="blue", bg_color="#DCDCDC", cursor="hand2") self.label_logs = ctk.CTkLabel(self.tabview_bottom.tab("日志"), width=100, font=self.f_pager, textvariable=self.label_pages_logs, text_color="blue", bg_color="#DCDCDC", cursor="hand2")
self.btn_previous = ctk.CTkButton(self.tabview_bottom.tab("日志"), width=60, text="上一页", font=self.f_segbtn, fg_color="#979DA2", corner_radius=0, command=self.__get_previous_log) self.btn_previous = ctk.CTkButton(self.tabview_bottom.tab("日志"), width=60, text="上一页", font=self.f_segbtn, fg_color="#979DA2", corner_radius=0, command=self.__get_previous_log)
self.btn_realtime = ctk.CTkButton(self.tabview_bottom.tab("日志"), width=60, text="实时", font=self.f_segbtn, fg_color="#979DA2", corner_radius=0, command=self.__get_realtime_log) self.btn_realtime = ctk.CTkButton(self.tabview_bottom.tab("日志"), width=60, text="实时", font=self.f_segbtn, fg_color="#979DA2", corner_radius=0, command=self.__get_realtime_log)
self.btn_next = ctk.CTkButton(self.tabview_bottom.tab("日志"), width=60, text="下一页", font=self.f_segbtn, fg_color="#979DA2", corner_radius=0, command=self.__get_next_log) self.btn_next = ctk.CTkButton(self.tabview_bottom.tab("日志"), width=60, text="下一页", font=self.f_segbtn, fg_color="#979DA2", corner_radius=0, command=self.__get_next_log)
self.btn_load = ctk.CTkButton(self.tabview_bottom.tab("日志"), width=60, text="加载", font=self.f_segbtn, fg_color="#979DA2", corner_radius=0, command=self.__load_log_db) self.btn_load = ctk.CTkButton(self.tabview_bottom.tab("日志"), width=60, text="加载", font=self.f_segbtn, fg_color="#979DA2", corner_radius=0, command=self.__load_log_db)
self.btn_find = ctk.CTkButton(self.tabview_bottom.tab("日志"), text="查找", width=60, font=self.f_segbtn, fg_color="#979DA2", corner_radius=0, cursor="hand2") self.btn_find = ctk.CTkButton(self.tabview_bottom.tab("日志"), text="查找", width=60, font=self.f_segbtn, fg_color="#979DA2", corner_radius=0, cursor="hand2")
self.entry_keyword = ctk.CTkEntry(self.tabview_bottom.tab("日志"), placeholder_text="[id/level/module] 查找内容", width=60, font=self.f_entry) self.entry_keyword = ctk.CTkEntry(self.tabview_bottom.tab("日志"), placeholder_text="[id/level/module] 查找内容", width=10, font=self.f_entry)
self.treeview_logs = ttk.Treeview(self.tabview_bottom.tab("日志"), height=1, columns=("id", "time", "level", "module", "content"), style="tv.Treeview", show="headings") self.treeview_logs = ttk.Treeview(self.tabview_bottom.tab("日志"), height=1, columns=("id", "time", "level", "module", "content"), style="tv.Treeview", show="headings")
self.y_scrollbar_logs = tk.Scrollbar(self.tabview_bottom.tab("日志"), width=30, command=self.treeview_logs.yview) self.y_scrollbar_logs = tk.Scrollbar(self.tabview_bottom.tab("日志"), width=30, command=self.treeview_logs.yview)
self.popupmenu_kw = tk.Menu(self.entry_keyword, tearoff=False) self.popupmenu_kw = tk.Menu(self.entry_keyword, tearoff=False)
@ -140,6 +162,29 @@ class App:
def __do_nothing(event): def __do_nothing(event):
... ...
@staticmethod
def __create_plot():
create_plot.main()
def __all_or_none(self):
if self.chk_box_v.get():
for widget in self.scrollable_frame.winfo_children():
widget.select()
else:
for widget in self.scrollable_frame.winfo_children():
widget.deselect()
def __get_curve_names(self):
curves = []
for widget in self.scrollable_frame.winfo_children():
if widget.get():
curves.append(widget.cget("text"))
return curves
def __copy_path(self, widget):
self.root.clipboard_clear()
self.root.clipboard_append(widget.get())
def __detect_network(self): def __detect_network(self):
def func_access(state): def func_access(state):
self.btn_robot_info.configure(state=state) self.btn_robot_info.configure(state=state)
@ -157,12 +202,12 @@ class App:
except Exception: except Exception:
self.btn_conn.configure(fg_color="#979DA2") self.btn_conn.configure(fg_color="#979DA2")
func_access("disabled") func_access("disabled")
time.sleep(3) time.sleep(2)
def __robot_info(self): def __robot_info(self):
def get_robot_info(): def get_robot_info():
self.tabview_bottom.set("输出") self.tabview_bottom.set("输出")
self.text_output.delete("1.0", "end") # self.text_output.delete("1.0", "end")
self.__w2t("正在获取机器信息,请稍后...\n\n") self.__w2t("正在获取机器信息,请稍后...\n\n")
infos = {0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}, 9: {}, 10: {}, 11: {}, 12: {}, 13: {}, 14: {}, 15: {}, 16: {}, 17: {}, 18: {}, 19: {}, 20: {}} infos = {0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}, 9: {}, 10: {}, 11: {}, 12: {}, 13: {}, 14: {}, 15: {}, 16: {}, 17: {}, 18: {}, 19: {}, 20: {}}
msg_id, state = clibs.c_hr.execution("controller.get_params") msg_id, state = clibs.c_hr.execution("controller.get_params")
@ -229,17 +274,23 @@ class App:
def __trig_estop(self): def __trig_estop(self):
def trig_estop(): def trig_estop():
self.tabview_bottom.set("输出") self.tabview_bottom.set("输出")
self.text_output.delete("1.0", "end")
self.__w2t("触发软急停信号已发送...\n") self.__w2t("触发软急停信号已发送...\n")
clibs.c_md.r_soft_estop(0) clibs.c_md.r_soft_estop(0)
try:
clibs.c_hr.execution("diagnosis.open", open=False, display_open=False, overrun=True, turn_area=True, delay_motion=False)
clibs.c_hr.execution("diagnosis.set_params", display_pdo_params=[], frequency=50, version="1.4.1")
except Exception as Err:
clibs.insert_logdb("WARNING", "aio", f"关闭诊断曲线失败 - {Err}")
if clibs.running:
clibs.running = False clibs.running = False
raise Exception("StopProcRunning") self.__w2t("程序已停止运行,执行过程中停止时需要清零后台数据,大约一分钟左右后再重新运行!!!\n", "red", "TerminateProgram")
self.__thread_it(trig_estop) self.__thread_it(trig_estop)
def __reset_estop(self): def __reset_estop(self):
def reset_estop(): def reset_estop():
self.tabview_bottom.set("输出") self.tabview_bottom.set("输出")
self.text_output.delete("1.0", "end") # self.text_output.delete("1.0", "end")
self.__w2t("解除软急停信号已发送...\n") self.__w2t("解除软急停信号已发送...\n")
clibs.c_md.r_soft_estop(1) clibs.c_md.r_soft_estop(1)
clibs.c_md.r_clear_alarm() clibs.c_md.r_clear_alarm()
@ -322,6 +373,14 @@ class App:
} }
return data return data
def get_data_dd():
data = {
"path": self.entry_path_ddv.get(),
"interval": self.entry_interval.get(),
"curves": self.__get_curve_names()
}
return data
def init_op(): def init_op():
if self.__is_running("开始") == "running": if self.__is_running("开始") == "running":
return "running" return "running"
@ -342,19 +401,19 @@ class App:
if init_op() == "running": if init_op() == "running":
return return
clibs.running = True
try:
if self.tabview_top.get() == "数据处理": if self.tabview_top.get() == "数据处理":
clibs.running = True
clibs.data_dp = get_data_dp() clibs.data_dp = get_data_dp()
try:
eval(clibs.data_dp["_main"] + ".main()") eval(clibs.data_dp["_main"] + ".main()")
finally:
clibs.running = False
clibs.stop = False
elif self.tabview_top.get() == "自动测试": elif self.tabview_top.get() == "自动测试":
clibs.running = True
clibs.data_at = get_data_at() clibs.data_at = get_data_at()
try:
eval("do_" + clibs.data_at["_main"] + ".main()") eval("do_" + clibs.data_at["_main"] + ".main()")
elif self.tabview_top.get() == "耐久记录":
clibs.data_dd = get_data_dd()
factory_test.main()
except Exception as Err:
self.__w2t(f"程序执行过程中出现异常,{Err}\n", "red")
finally: finally:
clibs.running = False clibs.running = False
clibs.stop = False clibs.stop = False
@ -593,6 +652,7 @@ class App:
self.f_logo = ("Segoe Script Bold", 28, "bold") self.f_logo = ("Segoe Script Bold", 28, "bold")
self.f_normal = ("Consolas", 20, "bold") self.f_normal = ("Consolas", 20, "bold")
self.f_segbtn = ("楷体", 20, "bold") self.f_segbtn = ("楷体", 20, "bold")
self.f_segbtn_l = ("楷体", 18, "bold")
self.f_common = ("Consolas", 18, "bold") self.f_common = ("Consolas", 18, "bold")
self.f_entry = ("Consolas", 16, "bold") self.f_entry = ("Consolas", 16, "bold")
self.f_text = ("仿宋", 16, "normal") self.f_text = ("仿宋", 16, "normal")
@ -643,11 +703,17 @@ class App:
self.__quit_preparation() self.__quit_preparation()
def __switch_tab_top(self): def __switch_tab_top(self):
... tab_name = self.tabview_top.get()
if tab_name == "数据处理" or tab_name == "自动测试":
self.tabview_top.configure(height=180)
elif tab_name == "耐久记录":
self.tabview_top.configure(height=330)
def __switch_tab_bottom(self): def __switch_tab_bottom(self):
if self.tabview_bottom.get() == "日志": if self.tabview_bottom.get() == "日志":
self.__get_realtime_log() self.__get_realtime_log()
elif self.tabview_bottom.get() == "输出":
self.text_output.see(ctk.END)
def __cut(self, widget): def __cut(self, widget):
try: try:
@ -704,6 +770,12 @@ class App:
self.entry_path_atv.set(dir_path) self.entry_path_atv.set(dir_path)
else: else:
self.entry_path_atv.set(c_origin) self.entry_path_atv.set(c_origin)
elif t_name == "耐久记录":
c_origin = self.entry_path_ddv.get()
if dir_path:
self.entry_path_ddv.set(dir_path)
else:
self.entry_path_ddv.set(c_origin)
def esc_quit_log_tl(event): def esc_quit_log_tl(event):
widget_toplevel = event.widget widget_toplevel = event.widget
@ -973,6 +1045,14 @@ class App:
def show_popupmenu_output(event): def show_popupmenu_output(event):
self.popupmenu_output.post(event.x_root, event.y_root) self.popupmenu_output.post(event.x_root, event.y_root)
def show_popupmenu_path(event):
if self.tabview_top.get() == "数据处理":
self.popupmenu_path_dp.post(event.x_root, event.y_root)
elif self.tabview_top.get() == "自动测试":
self.popupmenu_path_at.post(event.x_root, event.y_root)
elif self.tabview_top.get() == "耐久记录":
self.popupmenu_path_dd.post(event.x_root, event.y_root)
def conn_change(event): def conn_change(event):
def conn_or_disconn(): def conn_or_disconn():
if self.btn_conn.cget("fg_color") == "#979DA2": if self.btn_conn.cget("fg_color") == "#979DA2":
@ -987,9 +1067,6 @@ class App:
clibs.c_md = openapi.ModbusRequest(clibs.ip_addr, clibs.modbus_port) clibs.c_md = openapi.ModbusRequest(clibs.ip_addr, clibs.modbus_port)
clibs.c_hr = openapi.HmiRequest(clibs.ip_addr, clibs.socket_port, clibs.xService_port) clibs.c_hr = openapi.HmiRequest(clibs.ip_addr, clibs.socket_port, clibs.xService_port)
clibs.c_pd = openapi.PreDos(clibs.ip_addr, clibs.ssh_port, clibs.username, clibs.password) clibs.c_pd = openapi.PreDos(clibs.ip_addr, clibs.ssh_port, clibs.username, clibs.password)
# clibs.c_md.write_speed_max(123.456)
# clibs.c_hr.execution('state.set_tp_mode', tp_mode='without')
self.btn_conn.configure(state="normal", fg_color="#2E8B57") self.btn_conn.configure(state="normal", fg_color="#2E8B57")
except Exception: except Exception:
self.btn_conn.configure(state="normal", fg_color="#979DA2") self.btn_conn.configure(state="normal", fg_color="#979DA2")
@ -1052,6 +1129,7 @@ class App:
self.om_sensor_dp.grid(row=1, column=10, padx=(0, 10), pady=(0, 10)) self.om_sensor_dp.grid(row=1, column=10, padx=(0, 10), pady=(0, 10))
self.entry_path_dp.bind("<Button-1>", select_path) self.entry_path_dp.bind("<Button-1>", select_path)
self.entry_path_dp.bind("<Button-3>", show_popupmenu_path, add="+")
self.om_vel_dp.set("1") self.om_vel_dp.set("1")
self.om_trq_dp.set("2") self.om_trq_dp.set("2")
self.om_trqh_dp.set("2") self.om_trqh_dp.set("2")
@ -1072,13 +1150,27 @@ class App:
self.btn_trig_estop.grid(row=0, column=1, padx=(0, 10), pady=0) self.btn_trig_estop.grid(row=0, column=1, padx=(0, 10), pady=0)
self.btn_reset_estop.grid(row=0, column=2, padx=(0, 10), pady=0) self.btn_reset_estop.grid(row=0, column=2, padx=(0, 10), pady=0)
# self.om_main_at.bind("<<ComboboxSelected>>", change_sub_at)
self.om_main_at.bind("<Button-1>", change_sub_at) self.om_main_at.bind("<Button-1>", change_sub_at)
self.entry_path_at.bind("<Button-1>", select_path) self.entry_path_at.bind("<Button-1>", select_path)
self.entry_path_at.bind("<Button-3>", show_popupmenu_path, add="+")
self.entry_ip_at.bind("<Button-3>", show_popupmenu_ip, add="+") self.entry_ip_at.bind("<Button-3>", show_popupmenu_ip, add="+")
self.btn_conn.bind("<Button-1>", conn_change) self.btn_conn.bind("<Button-1>", conn_change)
self.btn_conn.bind("<Double-1>", self.__do_nothing, add="+") self.btn_conn.bind("<Double-1>", self.__do_nothing, add="+")
# ======================================================================== # ========================================================================
self.tabview_top.tab("耐久记录").grid_columnconfigure(2, weight=1)
self.scrollable_frame.grid(row=0, column=0, rowspan=30, padx=10, pady=0)
self.switch_1.grid(row=0, column=0, padx=10, sticky="w")
self.switch_2.grid(row=1, column=0, padx=10, sticky="w")
self.label_path_dd.grid(row=0, column=1, padx=10, pady=0, sticky="w")
self.entry_path_dd.grid(row=0, column=2, padx=(0, 10), pady=0, sticky="we")
self.label_interval.grid(row=1, column=1, padx=10, pady=0, sticky="w")
self.entry_interval.grid(row=1, column=2, padx=(0, 10), pady=0, sticky="we")
self.btn_plot.grid(row=2, column=1, padx=10, pady=(5, 0), sticky="w")
self.chk_box.grid(row=2, column=2, padx=(0, 10), pady=(5, 0), sticky="e")
self.entry_path_dd.bind("<Button-1>", select_path)
self.entry_path_dd.bind("<Button-3>", show_popupmenu_path, add="+")
# ========================================================================
self.tabview_bottom.tab("输出").grid_rowconfigure(0, weight=1) self.tabview_bottom.tab("输出").grid_rowconfigure(0, weight=1)
self.tabview_bottom.tab("输出").grid_columnconfigure(0, weight=1) self.tabview_bottom.tab("输出").grid_columnconfigure(0, weight=1)
self.text_output.grid(row=0, column=0, sticky="news") self.text_output.grid(row=0, column=0, sticky="news")

View File

@ -10,7 +10,7 @@ from common import clibs, openapi
def initialization(path, sub, data_dirs, data_files, hr, w2t): def initialization(path, sub, data_dirs, data_files, hr, w2t):
def check_files(): def check_files():
msg = "初始路径下不允许有文件夹,初始路径下只能存在如下五个文件,确认后重新运行!\n" msg = "初始路径下不允许有文件夹,初始路径下只能存在如下五个文件,且文件为关闭状态,确认后重新运行!\n"
msg += "1. configs.xlsx\n2. reach33/reach66/reach100_xxxx.xlsx\n3. xxxx.zip\n" msg += "1. configs.xlsx\n2. reach33/reach66/reach100_xxxx.xlsx\n3. xxxx.zip\n"
if len(data_dirs) != 0 or len(data_files) != 5: if len(data_dirs) != 0 or len(data_files) != 5:
w2t(msg, "red", "InitFileError") w2t(msg, "red", "InitFileError")
@ -130,16 +130,19 @@ def gen_result_file(path, axis, t_end, reach, load, speed, speed_target, rounds,
df.to_csv(filename, sep="\t", index=False) df.to_csv(filename, sep="\t", index=False)
def change_curve_state(hr, stat_1, stat_2): def change_curve_state(hr, 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 = [{"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}) display_pdo_params.append({"name": "device_safety_estop", "channel": 0})
hr.execution("diagnosis.open", open=stat_1, display_open=stat_2, overrun=True, turn_area=True, delay_motion=False) hr.execution("diagnosis.open", open=stat, display_open=stat, overrun=True, turn_area=True, delay_motion=False)
hr.execution("diagnosis.set_params", display_pdo_params=display_pdo_params, frequency=50, version="1.4.1") hr.execution("diagnosis.set_params", display_pdo_params=display_pdo_params, frequency=50, version="1.4.1")
def run_rl(path, sub, hr, md, config_file, prj_file, result_dirs, avs, w2t): def run_rl(path, sub, hr, md, config_file, prj_file, result_dirs, avs, w2t):
count, total, speed_target = 0, 63, 0 count, total, speed_target = 0, 63, 0
prj_name = prj_file.split("/")[-1].split(".")[0] prj_name = ".".join(prj_file.split("/")[-1].split(".")[:-1])
wb = openpyxl.load_workbook(config_file, read_only=True) wb = openpyxl.load_workbook(config_file, read_only=True)
ws = wb["Target"] ws = wb["Target"]
write_diagnosis = float(ws.cell(row=2, column=2).value) write_diagnosis = float(ws.cell(row=2, column=2).value)
@ -171,6 +174,10 @@ def run_rl(path, sub, hr, md, config_file, prj_file, result_dirs, avs, w2t):
continue continue
for axis in range(1, 4): for axis in range(1, 4):
if not clibs.running:
w2t("后台数据清零完成,现在可以重新运行之前停止的程序。", "red")
exit()
# for single condition test # for single condition test
if (single_axis != -1 and single_axis != axis) or (axis == 3 and reach != "100"): if (single_axis != -1 and single_axis != axis) or (axis == 3 and reach != "100"):
continue continue
@ -231,16 +238,16 @@ def run_rl(path, sub, hr, md, config_file, prj_file, result_dirs, avs, w2t):
break break
else: else:
time.sleep(1) time.sleep(1)
if (time.time() - t_start) > 20: if (time.time() - t_start) > 3:
w2t("20s 内未收到机器人的运行信号,需要确认 RL 程序编写正确并正常执行...", "red", "ReadySignalTimeoutError") w2t("3s 内未收到机器人的运行信号,需要确认 RL 程序编写正确并正常执行...", "red", "ReadySignalTimeoutError")
# 4. 找出最大速度传递给RL程序最后清除相关记录 # 4. 找出最大速度传递给RL程序最后清除相关记录
time.sleep(5) # 消除前 5s 的不稳定数据 time.sleep(5) # 消除前 5s 的不稳定数据
change_curve_state(hr, True, True) change_curve_state(hr, True)
start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
time.sleep(get_init_speed) # 指定时间后获取实际【正|负】方向的最大速度可通过configs.xlsx配置 time.sleep(get_init_speed) # 指定时间后获取实际【正|负】方向的最大速度可通过configs.xlsx配置
end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
hr.execution("rl_task.stop", tasks=["brake"]) hr.execution("rl_task.stop", tasks=["brake"])
change_curve_state(hr, False, False) change_curve_state(hr, False)
# 找出最大速度 # 找出最大速度
@clibs.db_lock @clibs.db_lock
@ -297,7 +304,7 @@ def run_rl(path, sub, hr, md, config_file, prj_file, result_dirs, avs, w2t):
def exec_brake(): def exec_brake():
flag, start, data, record = True, time.time(), None, None flag, start, data, record = True, time.time(), None, None
change_curve_state(hr, True, True) change_curve_state(hr, True)
while flag: while flag:
time.sleep(0.05) time.sleep(0.05)
if time.time() - start > 20: if time.time() - start > 20:
@ -320,7 +327,7 @@ def run_rl(path, sub, hr, md, config_file, prj_file, result_dirs, avs, w2t):
if (pon == "positive" and speed_moment > 0) or (pon == "negative" and speed_moment < 0): if (pon == "positive" and speed_moment > 0) or (pon == "negative" and speed_moment < 0):
clibs.c_ec.setdo_value(io_name, "false") clibs.c_ec.setdo_value(io_name, "false")
time.sleep(2) time.sleep(2)
change_curve_state(hr, False, False) change_curve_state(hr, False)
flag = False flag = False
return time.time() return time.time()

View File

@ -8,7 +8,7 @@ from common import clibs
def initialization(path, sub, data_dirs, data_files, hr, w2t): def initialization(path, sub, data_dirs, data_files, hr, w2t):
def check_files(): def check_files():
msg = "初始路径下不允许有文件夹,初始路径下只能存在如下两个文件,确认后重新运行!\n" msg = "初始路径下不允许有文件夹,初始路径下只能存在如下两个文件,且文件为关闭状态,确认后重新运行!\n"
msg += "1. T_电机电流.xlsx\n2. xxxx.zip\n" msg += "1. T_电机电流.xlsx\n2. xxxx.zip\n"
if len(data_dirs) != 0 or len(data_files) != 2: if len(data_dirs) != 0 or len(data_files) != 2:
w2t(msg, "red", "InitFileError") w2t(msg, "red", "InitFileError")
@ -60,7 +60,7 @@ def initialization(path, sub, data_dirs, data_files, hr, w2t):
def single_axis_proc(path, records, number): def single_axis_proc(path, records, number):
text = "single" if number < 6 else "hold" text = "single" if number < 6 else "hold"
number = number if number < 6 else number - 6 number = number if number < 6 else number - 6
d_vel, d_trq, d_sensor = [], [], [] d_vel, d_trq, d_sensor, d_trans = [], [], [], []
for record in records: for record in records:
data = eval(record[0])["data"] data = eval(record[0])["data"]
for item in data: for item in data:
@ -71,33 +71,40 @@ def single_axis_proc(path, records, number):
d_trq.extend(d_item) d_trq.extend(d_item)
elif item.get("channel", None) == number and item.get("name", None) == "hw_sensor_trq_feedback": elif item.get("channel", None) == number and item.get("name", None) == "hw_sensor_trq_feedback":
d_sensor.extend(d_item) 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}) df1 = pandas.DataFrame.from_dict({"hw_joint_vel_feedback": d_vel})
df2 = pandas.DataFrame.from_dict({"device_servo_trq_feedback": d_trq}) df2 = pandas.DataFrame.from_dict({"device_servo_trq_feedback": d_trq})
df3 = pandas.DataFrame.from_dict({"hw_sensor_trq_feedback": d_sensor}) df3 = pandas.DataFrame.from_dict({"hw_sensor_trq_feedback": d_sensor})
df = pandas.concat([df1, df2, df3], axis=1) df4 = pandas.DataFrame.from_dict({"hw_estimate_trans_trq_res": d_trans})
df = pandas.concat([df1, df2, df3, df4], axis=1)
filename = f"{path}/single/j{number + 1}_{text}_{time.time()}.data" filename = f"{path}/single/j{number + 1}_{text}_{time.time()}.data"
df.to_csv(filename, sep="\t", index=False) df.to_csv(filename, sep="\t", index=False)
def scenario_proc(path, records, number, scenario_time): def scenario_proc(path, records, number, scenario_time):
for axis in range(6): d_vel, d_trq, d_sensor, d_trans = [[], [], [], [], [], []], [[], [], [], [], [], []], [[], [], [], [], [], []], [[], [], [], [], [], []]
d_vel, d_trq, d_sensor = [], [], []
for record in records: for record in records:
data = eval(record[0])["data"] data = eval(record[0])["data"]
for item in data: for item in data:
d_item = reversed(item["value"]) 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": if item.get("channel", None) == axis and item.get("name", None) == "hw_joint_vel_feedback":
d_vel.extend(d_item) d_vel[axis].extend(d_item)
elif item.get("channel", None) == axis and item.get("name", None) == "device_servo_trq_feedback": elif item.get("channel", None) == axis and item.get("name", None) == "device_servo_trq_feedback":
d_trq.extend(d_item) d_trq[axis].extend(d_item)
elif item.get("channel", None) == axis and item.get("name", None) == "hw_sensor_trq_feedback": elif item.get("channel", None) == axis and item.get("name", None) == "hw_sensor_trq_feedback":
d_sensor.extend(d_item) 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)
df1 = pandas.DataFrame.from_dict({"hw_joint_vel_feedback": d_vel}) for axis in range(6):
df2 = pandas.DataFrame.from_dict({"device_servo_trq_feedback": d_trq}) df1 = pandas.DataFrame.from_dict({"hw_joint_vel_feedback": d_vel[axis]})
df3 = pandas.DataFrame.from_dict({"hw_sensor_trq_feedback": d_sensor}) df2 = pandas.DataFrame.from_dict({"device_servo_trq_feedback": d_trq[axis]})
df = pandas.concat([df1, df2, df3], axis=1) 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"{path}/s_{number-11}/j{axis+1}_s_{number-11}_{scenario_time}_{time.time()}.data" filename = f"{path}/s_{number-11}/j{axis+1}_s_{number-11}_{scenario_time}_{time.time()}.data"
df.to_csv(filename, sep="\t", index=False) df.to_csv(filename, sep="\t", index=False)
@ -121,14 +128,15 @@ def gen_result_file(path, number, start_time, end_time, scenario_time):
p.start() p.start()
def change_curve_state(hr, stat_1, stat_2): def change_curve_state(hr, stat):
display_pdo_params = [{"name": name, "channel": chl} for name in ["hw_joint_vel_feedback", "device_servo_trq_feedback", "hw_sensor_trq_feedback"] for chl in range(6)] curves = ["hw_joint_vel_feedback", "device_servo_trq_feedback", "hw_sensor_trq_feedback", "hw_estimate_trans_trq_res"]
hr.execution("diagnosis.open", open=stat_1, display_open=stat_2, overrun=True, turn_area=True, delay_motion=False) display_pdo_params = [] if not stat else [{"name": curve, "channel": chl} for curve in curves for chl in range(6)]
hr.execution("diagnosis.open", open=stat, display_open=stat, overrun=True, turn_area=True, delay_motion=False)
hr.execution("diagnosis.set_params", display_pdo_params=display_pdo_params, frequency=50, version="1.4.1") hr.execution("diagnosis.set_params", display_pdo_params=display_pdo_params, frequency=50, version="1.4.1")
def run_rl(path, prj_file, hr, md, sub, w2t): def run_rl(path, prj_file, hr, md, sub, w2t):
prj_name = prj_file.split("/")[-1].split(".")[0] prj_name = ".".join(prj_file.split("/")[-1].split(".")[:-1])
c_regular = [ c_regular = [
"scenario(0, j1_p, j1_n, p_speed, p_tool, i_tool)", "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, j2_p, j2_n, p_speed, p_tool, i_tool)",
@ -165,6 +173,10 @@ def run_rl(path, prj_file, hr, md, sub, w2t):
md.r_clear_alarm() md.r_clear_alarm()
for condition in conditions: for condition in conditions:
if not clibs.running:
w2t("后台数据清零完成,现在可以重新运行之前停止的程序。", "red")
exit()
number = conditions.index(condition) number = conditions.index(condition)
w2t(f"正在执行{disc[number]}测试......\n") w2t(f"正在执行{disc[number]}测试......\n")
@ -198,12 +210,12 @@ def run_rl(path, prj_file, hr, md, sub, w2t):
break break
else: else:
time.sleep(1) time.sleep(1)
if (time.time() - t_start) > 20: if (time.time() - t_start) > 3:
w2t("20s 内未收到机器人的运行信号需要确认RL程序和工具通信是否正常执行...", "red", "ReadySignalTimeoutError") w2t("3s 内未收到机器人的运行信号需要确认RL程序和工具通信是否正常执行...", "red", "ReadySignalTimeoutError")
# 4. 执行采集 # 4. 执行采集
time.sleep(10) # 消除前 10s 的不稳定数据 time.sleep(10) # 消除前 10s 的不稳定数据
change_curve_state(hr, True, True) change_curve_state(hr, True)
start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
single_time, stall_time, scenario_time = 40, 10, 0 single_time, stall_time, scenario_time = 40, 10, 0
if number < 6: # 单轴 if number < 6: # 单轴
@ -227,7 +239,7 @@ def run_rl(path, prj_file, hr, md, sub, w2t):
end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
hr.execution("rl_task.stop", tasks=["current"]) hr.execution("rl_task.stop", tasks=["current"])
time.sleep(2) # 确保数据都拿到 time.sleep(2) # 确保数据都拿到
change_curve_state(hr, False, False) change_curve_state(hr, False)
gen_result_file(path, number, start_time, end_time, scenario_time) gen_result_file(path, number, start_time, end_time, scenario_time)
else: else:
if sub == "tool100": if sub == "tool100":
@ -252,7 +264,7 @@ def main():
e_time = time.time() e_time = time.time()
time_total = e_time - s_time time_total = e_time - s_time
w2t(f"-" * 90 + "\n", "purple") w2t(f"-" * 90 + "\n", "purple")
w2t(f"处理总时长:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s", "green") w2t(f"处理总时长:{time_total // 3600:02.0f} h {time_total % 3600 // 60:02.0f} m {time_total % 60:02.0f} s\n", "green")
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -66,42 +66,12 @@ def insert_logdb(_level, _module, _content):
cursor.execute("insert into logs (level, module, content) values (?, ?, ?)", data) cursor.execute("insert into logs (level, module, content) values (?, ?, ?)", data)
def insert_logdb_multi(data):
if db_state == "readwrite":
global conn, cursor, lock
# data = [_level, _module, repr(_content)]
# cursor.execute("insert into logs (level, module, content) values (?, ?, ?)", data)
try:
lock.acquire(True)
cursor.executemany("insert into logs (level, module, content) values (?, ?, ?)", data)
finally:
lock.release()
class GetThreadResult(threading.Thread):
def __init__(self, func, args=()):
super(GetThreadResult, self).__init__()
self.func = func
self.args = args
self.result = None
def run(self):
self.result = self.func(*self.args)
def get_result(self):
threading.Thread.join(self) # 等待线程执行完毕
try:
return self.result
except Exception:
return None
# PREFIX = 'assets' # for pyinstaller packaging # PREFIX = 'assets' # for pyinstaller packaging
PREFIX = '../assets' # for source code testing and debug PREFIX = '../assets' # for source code testing and debug
log_path = f"{PREFIX}/logs" log_path = f"{PREFIX}/logs"
levels = ["DEBUG", "INFO", "WARNING", "ERROR"] levels = ["DEBUG", "INFO", "WARNING", "ERROR"]
db_state = "readwrite" db_state = "readwrite"
data_dp, data_at = {}, {} data_dp, data_at, data_dd = {}, {}, {}
conn, cursor, w2t, tl_prg, f_records, stop, running = None, None, None, None, None, True, False conn, cursor, w2t, tl_prg, f_records, stop, running = None, None, None, None, None, True, False
ip_addr = "192.168.0.160" ip_addr = "192.168.0.160"

View File

@ -1,6 +1,5 @@
import json import json
import socket import socket
import inspect
from inspect import currentframe from inspect import currentframe
import threading import threading
import functools import functools
@ -10,9 +9,6 @@ import selectors
import time import time
from common import clibs from common import clibs
from os import listdir
from pymodbus.payload import BinaryPayloadBuilder
from pymodbus.constants import Endian
import os.path import os.path
from ctypes import * from ctypes import *
import hashlib import hashlib
@ -161,12 +157,12 @@ class ModbusRequest(object):
clibs.insert_logdb("INFO", "openapi", f"modbus: 40102 将 {pon} 写入") clibs.insert_logdb("INFO", "openapi", f"modbus: 40102 将 {pon} 写入")
def write_axis(self, axis): def write_axis(self, axis):
result = self.__c.convert_to_registers(int(axis), self.__c.DATATYPE.INT32, word_order="little") result = self.__c.convert_to_registers(int(axis), self.__c.DATATYPE.INT32, "little")
self.__c.write_registers(40103, result) self.__c.write_registers(40103, result)
clibs.insert_logdb("INFO", "openapi", f"modbus: 40103 将 {axis} 写入") clibs.insert_logdb("INFO", "openapi", f"modbus: 40103 将 {axis} 写入")
def write_speed_max(self, speed): def write_speed_max(self, speed):
result = self.__c.convert_to_registers(float(speed), self.__c.DATATYPE.FLOAT32, word_order="little") result = self.__c.convert_to_registers(float(speed), self.__c.DATATYPE.FLOAT32, "little")
self.__c.write_registers(40105, result) self.__c.write_registers(40105, result)
clibs.insert_logdb("INFO", "openapi", f"modbus: 40105 将 {speed} 写入") clibs.insert_logdb("INFO", "openapi", f"modbus: 40105 将 {speed} 写入")
@ -315,7 +311,7 @@ class ModbusRequest(object):
def read_scenario_time(self): def read_scenario_time(self):
results = self.__c.read_holding_registers(40601, count=2) results = self.__c.read_holding_registers(40601, count=2)
result = self.__c.convert_from_registers(results.registers, data_type=self.__c.DATATYPE.FLOAT32, word_order="little") result = self.__c.convert_from_registers(registers=results.registers, data_type=self.__c.DATATYPE.FLOAT32, word_order="little")
return result return result
def read_brake_done(self): def read_brake_done(self):
@ -750,7 +746,7 @@ class HmiRequest(object):
req = json.load(f_json) req = json.load(f_json)
except Exception as Err: except Exception as Err:
clibs.insert_logdb("ERROR", "openapi", f"hr-execution: 暂不支持 {command} 功能,或确认该功能存在... {Err}") clibs.insert_logdb("ERROR", "openapi", f"hr-execution: 暂不支持 {command} 功能,或确认该功能存在... {Err}")
clibs.w2t(f"hr-execution: 暂不支持 {command} 功能,或确认该功能存在... {Err}", "red") clibs.w2t(f"hr-execution: 暂不支持 {command} 功能,或确认该功能存在... {Err}", "red", "CommandError")
if p_flag == 0: # for old protocols if p_flag == 0: # for old protocols
match command: match command:

View File

@ -66,9 +66,6 @@ def get_configs(config_file, w2t):
try: try:
with open(config_file, mode="r", encoding="utf-8") as f_config: with open(config_file, mode="r", encoding="utf-8") as f_config:
configs = json.load(f_config) configs = json.load(f_config)
except Exception as Err:
clibs.insert_logdb("ERROR", "current", f"get_config: 无法打开 {config_file},获取配置文件参数错误 {Err}")
w2t(f"无法打开 {config_file}", color="red", desc="OpenFileError")
p_dir = config_file.split('/')[-2] p_dir = config_file.split('/')[-2]
if not re.match("^[jJ][123]$", p_dir): if not re.match("^[jJ][123]$", p_dir):
@ -81,6 +78,9 @@ def get_configs(config_file, w2t):
av = avs[axis-1] av = avs[axis-1]
return av, rr return av, rr
except Exception as Err:
clibs.insert_logdb("ERROR", "current", f"get_config: 无法打开 {config_file},或获取配置文件参数错误 {Err}")
w2t(f"无法打开 {config_file},或者使用了错误的机型配置文件,需检查\n", color="red", desc="OpenFileError")
def now_doing_msg(docs, flag, w2t): def now_doing_msg(docs, flag, w2t):

View File

@ -32,8 +32,8 @@ def initialization(path, w2t, insert_logdb):
return data_files, config_file return data_files, config_file
def current_max(data_files, rcs, trq, w2t, insert_logdb): def current_max(data_files, rts, trq, w2t, insert_logdb):
insert_logdb("INFO", "current", "MAX: 正在处理最大电流值逻辑...") insert_logdb("INFO", "current", "MAX: 正在处理最大转矩值逻辑...")
current = {1: [], 2: [], 3: [], 4: [], 5: [], 6: []} current = {1: [], 2: [], 3: [], 4: [], 5: [], 6: []}
for data_file in data_files: for data_file in data_files:
if data_file.endswith(".data"): if data_file.endswith(".data"):
@ -44,17 +44,17 @@ def current_max(data_files, rcs, trq, w2t, insert_logdb):
insert_logdb("INFO", "current", f"MAX: 正在处理 {data_file}") insert_logdb("INFO", "current", f"MAX: 正在处理 {data_file}")
cols = len(df.columns) cols = len(df.columns)
axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j")) axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j"))
rca = rcs[axis-1] rt = rts[axis-1]
insert_logdb("INFO", "current", f"MAX: 最大列数为 {cols}{axis} 轴的额定电流为 {rca}") insert_logdb("INFO", "current", f"MAX: 最大列数为 {cols}{axis} 轴的额定转矩为 {rt}")
col = df.columns.values[trq-1] # 获取 "device_servo_trq_feedback" col = df.columns.values[trq-1] # 获取 "device_servo_trq_feedback"
c_max = df[col].abs().max() c_max = df[col].abs().max()
scale = 1000 scale = 1000
_ = abs(c_max/scale*rca) _ = abs(c_max/scale*rt)
current[axis].append(_) current[axis].append(_)
w2t(f"{data_file}: {_:.4f}\n") w2t(f"{data_file}: {_:.2f}\n")
insert_logdb("INFO", "current", f"MAX: 获取到的列名为 {col},最大电流{_}") insert_logdb("INFO", "current", f"MAX: 获取到的列名为 {col},最大转矩{_}")
with open(data_file, "a+") as f_data: with open(data_file, "a+") as f_data:
csv_writer = csv.writer(f_data, delimiter="\t") csv_writer = csv.writer(f_data, delimiter="\t")
@ -69,12 +69,12 @@ def current_max(data_files, rcs, trq, w2t, insert_logdb):
w2t(f"{value:.4f} ") w2t(f"{value:.4f} ")
w2t("\n") w2t("\n")
w2t("\n【MAX】数据处理完毕......") w2t("\n【MAX】数据处理完毕......")
insert_logdb("INFO", "current", f"MAX: 获取最大电流值结束 current_max = {current}") insert_logdb("INFO", "current", f"MAX: 获取最大转矩值结束 current_max = {current}")
return current return current
def current_avg(data_files, rcs, trqh, w2t, insert_logdb): def current_avg(data_files, rts, trqh, w2t, insert_logdb):
insert_logdb("INFO", "current", "AVG: 正在处理平均电流值逻辑...") insert_logdb("INFO", "current", "AVG: 正在处理平均转矩值逻辑...")
current = {1: [], 2: [], 3: [], 4: [], 5: [], 6: []} current = {1: [], 2: [], 3: [], 4: [], 5: [], 6: []}
for data_file in data_files: for data_file in data_files:
if data_file.endswith(".data"): if data_file.endswith(".data"):
@ -85,19 +85,19 @@ def current_avg(data_files, rcs, trqh, w2t, insert_logdb):
insert_logdb("INFO", "current", f"AVG: 正在处理 {data_file}") insert_logdb("INFO", "current", f"AVG: 正在处理 {data_file}")
cols = len(df.columns) cols = len(df.columns)
axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j")) axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j"))
rca = rcs[axis-1] rt = rts[axis-1]
insert_logdb("INFO", "current", f"AVG: 最大列数为 {cols}{axis} 轴的额定电流为 {rca}") insert_logdb("INFO", "current", f"AVG: 最大列数为 {cols}{axis} 轴的额定转矩为 {rt}")
col = df.columns.values[trqh-1] col = df.columns.values[trqh-1]
c_std = df[col].std() c_std = df[col].std()
c_avg = df[col].mean() c_avg = df[col].mean()
scale = 1000 scale = 1000
_ = (abs(c_avg)+c_std*3)/scale*rca _ = (abs(c_avg)+c_std*3)/scale*rt
current[axis].append(_) current[axis].append(_)
w2t(f"{data_file}: {_:.4f}\n") w2t(f"{data_file}: {_:.2f}\n")
insert_logdb("INFO", "current", f"AVG: 获取到的列名为 {col},平均电流{_}") insert_logdb("INFO", "current", f"AVG: 获取到的列名为 {col},平均转矩{_}")
with open(data_file, "a+") as f_data: with open(data_file, "a+") as f_data:
csv_writer = csv.writer(f_data, delimiter="\t") csv_writer = csv.writer(f_data, delimiter="\t")
csv_writer.writerow([""] * (cols-1) + [_]) csv_writer.writerow([""] * (cols-1) + [_])
@ -111,11 +111,11 @@ def current_avg(data_files, rcs, trqh, w2t, insert_logdb):
w2t(f"{value:.4f} ") w2t(f"{value:.4f} ")
w2t("\n") w2t("\n")
w2t("\n【AVG】数据处理完毕......\n") w2t("\n【AVG】数据处理完毕......\n")
insert_logdb("INFO", "current", f"AVG: 获取平均电流值结束 current_avg = {current}") insert_logdb("INFO", "current", f"AVG: 获取平均转矩值结束 current_avg = {current}")
return current return current
def current_cycle(data_files, vel, trq, trqh, sensor, rrs, rcs, params, w2t, insert_logdb): def current_cycle(data_files, vel, trq, trqh, sensor, rrs, rts, params, w2t, insert_logdb):
result, hold, single, scenario, dur_time = None, [], [], [], 0 result, hold, single, scenario, dur_time = None, [], [], [], 0
for data_file in data_files: for data_file in data_files:
filename = data_file.split("/")[-1] filename = data_file.split("/")[-1]
@ -137,16 +137,17 @@ def current_cycle(data_files, vel, trq, trqh, sensor, rrs, rcs, params, w2t, ins
wb = openpyxl.load_workbook(result) wb = openpyxl.load_workbook(result)
ws = wb["统计"] ws = wb["统计"]
for idx in range(len(params)): for idx in range(len(params)-1):
row = idx + 2 row = idx + 2
for col in range(2, 8): for col in range(2, 8):
ws.cell(row=row, column=col).value = params[idx][col-2] ws.cell(row=row, column=col).value = params[idx][col-2]
ws.cell(row=1, column=1).value = params[-1]
if hold: if hold:
avg = current_avg(hold, rcs, trqh, w2t, insert_logdb) avg = current_avg(hold, rts, trqh, w2t, insert_logdb)
for axis, cur_value in avg.items(): for axis, cur_value in avg.items():
sht_name = f"J{axis}" sht_name = f"J{axis}"
wb[sht_name]["O4"].value = float(cur_value[0]) wb[sht_name]["P4"].value = float(cur_value[0])
if dur_time == 0: if dur_time == 0:
p_single(wb, single, vel, trq, sensor, rrs, w2t, insert_logdb) p_single(wb, single, vel, trq, sensor, rrs, w2t, insert_logdb)
@ -303,29 +304,31 @@ def p_single(wb, single, vel, trq, sensor, rrs, w2t, insert_logdb):
if abs(row_end+row_start-2*row_middle) > 1000: if abs(row_end+row_start-2*row_middle) > 1000:
insert_logdb("WARNING", "current", f"{axis} 轴数据占空比异常!") insert_logdb("WARNING", "current", f"{axis} 轴数据占空比异常!")
data, first_c, second_c, third_c = [], vel-1, trq-1, sensor-1 data, first_c, second_c, third_c, fourth_c = [], vel-1, trq-1, sensor-1, sensor
for row in range(row_start, row_end+1): for row in range(row_start, row_end+1):
data.append(df_origin.iloc[row, first_c]) data.append(df_origin.iloc[row, first_c])
data.append(df_origin.iloc[row, second_c]) data.append(df_origin.iloc[row, second_c])
data.append(df_origin.iloc[row, third_c]) data.append(df_origin.iloc[row, third_c])
data.append(df_origin.iloc[row, fourth_c])
i = 0 i = 0
for row in ws.iter_rows(min_row=2, min_col=2, max_row=150000, max_col=4): for row in ws.iter_rows(min_row=2, min_col=2, max_row=150000, max_col=5):
for cell in row: for cell in row:
try: try:
if i % 3 == 0: if i % 4 == 0:
ws.cell((i//3)+2, 1).value = float(((i//3)+1)/1000) ws.cell((i//4)+2, 1).value = float(((i//4)+1)/1000)
_ = f"{data[i]:.2f}" _ = f"{data[i]:.2f}"
cell.value = float(_) cell.value = float(_)
i += 1 i += 1
except Exception: except Exception:
if i % 3 == 0: if i % 4 == 0:
ws.cell((i//3)+2, 1).value = None ws.cell((i//4)+2, 1).value = None
cell.value = None cell.value = None
i += 1 i += 1
def p_scenario(wb, scenario, vel, trq, sensor, rrs, dur_time, w2t): def p_scenario(wb, scenario, vel, trq, sensor, rrs, dur_time, w2t):
w2t(f"本次处理的是电机电流场景数据,场景运动周期为 {dur_time}s\n", "blue")
for data_file in scenario: for data_file in scenario:
cycle = 0.001 cycle = 0.001
axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j")) axis = int(data_file.split("/")[-1].split("_")[0].removeprefix("j"))
@ -344,64 +347,60 @@ def p_scenario(wb, scenario, vel, trq, sensor, rrs, dur_time, w2t):
if row_end > df.index[-1]: if row_end > df.index[-1]:
w2t(f"位置超限:{data_file} 共有 {df.index[-1]} 条数据,无法取到第 {row_end} 条数据,需要确认场景周期时间...", "red", "DataOverLimit") w2t(f"位置超限:{data_file} 共有 {df.index[-1]} 条数据,无法取到第 {row_end} 条数据,需要确认场景周期时间...", "red", "DataOverLimit")
data, first_c, second_c, third_c = [], vel-1, trq-1, sensor-1 data, first_c, second_c, third_c, fourth_c = [], vel-1, trq-1, sensor-1, sensor
for row in range(row_start, row_end+1): for row in range(row_start, row_end+1):
data.append(df_origin.iloc[row, first_c]) data.append(df_origin.iloc[row, first_c])
data.append(df_origin.iloc[row, second_c]) data.append(df_origin.iloc[row, second_c])
data.append(df_origin.iloc[row, third_c]) data.append(df_origin.iloc[row, third_c])
data.append(df_origin.iloc[row, fourth_c])
i = 0 i = 0
for row in ws.iter_rows(min_row=2, min_col=2, max_row=250000, max_col=4): for row in ws.iter_rows(min_row=2, min_col=2, max_row=250000, max_col=5):
for cell in row: for cell in row:
try: try:
if i % 3 == 0: if i % 4 == 0:
ws.cell((i//3)+2, 1).value = float(((i//3)+1)/1000) ws.cell((i//4)+2, 1).value = float(((i//4)+1)/1000)
_ = f"{data[i]:.2f}" _ = f"{data[i]:.2f}"
cell.value = float(_) cell.value = float(_)
i += 1 i += 1
except Exception: except Exception:
cell.value = None cell.value = None
if i % 3 == 0: if i % 4 == 0:
ws.cell((i//3)+2, 1).value = None ws.cell((i//4)+2, 1).value = None
i += 1 i += 1
def get_configs(config_file, w2t, insert_logdb): def get_configs(config_file, w2t, insert_logdb):
try: 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: with open(config_file, mode="r", encoding="utf-8") as f_config:
configs = json.load(f_config) configs = json.load(f_config)
except Exception as Err:
insert_logdb("ERROR", "current", f"get_config: 无法打开 {config_file},获取配置文件参数错误 {Err}")
w2t(f"无法打开 {config_file}", color="red", desc="OpenFileError")
# 最大角速度,额定电流,减速比,额定转速
version = configs["VERSION"] 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_rts = configs["MOTOR"]["RATED_TORQUE"] # 电机额定转矩rt for rated torque
m_max_ts = configs["MOTOR"]["PEAK_TORQUE"] # 电机峰值转矩 m_max_ts = configs["MOTOR"]["PEAK_TORQUE"] # 电机峰值转矩
m_stall_ts = configs["MOTOR"]["STALL_TORQUE"] # 电机堵转转矩
m_tcs = [1, 1, 1, 1, 1, 1] # 电机转矩常数tc for torque constant
m_rcs, m_max_cs, m_stall_cs = [], [], []
for i in range(len(m_tcs)):
m_rcs.append(m_rts[i]/m_tcs[i]) # 电机额定电流rc for rated current
m_max_cs.append(m_max_ts[i]/m_tcs[i]) # 电机最大电流
m_stall_cs.append(m_stall_ts[i]/m_tcs[i]) # 电机堵转电流
m_r_rpms = configs["MOTOR"]["RATED_SPEED"] # 电机额定转速 m_r_rpms = configs["MOTOR"]["RATED_SPEED"] # 电机额定转速
m_max_rpms = configs["MOTOR"]["MAX_SPEED"] # 电机最大转速 m_max_rpms = configs["MOTOR"]["MAX_SPEED"] # 电机最大转速
r_rrs = [abs(_) for _ in configs["TRANSMISSION"]["REDUCTION_RATIO_NUMERATOR"]] # 减速比rr for reduction ratio
r_max_sst = configs["TRANSMISSION"]["MAX_TORQUE_FOR_START_AND_STOP"] # 减速器最大启停转矩sst for start and stop torque 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_max_t = configs["TRANSMISSION"]["MAX_PEAK_TORQUE"] # 减速器瞬时最大转矩
sc = [0.001, 0.001, 0.001, 0.001, 0.001, 0.001] # 采样周期sc for sample cycle
r_rts = [1, 1, 1, 1, 1, 1] # 减速器额定转矩
r_r_rpms = [1, 1, 1, 1, 1, 1] # 减速器额定转速
r_life_cycle = [10000, 10000, 10000, 10000, 10000, 10000] # 减速器L10寿命
r_avg_t = configs["TRANSMISSION"]["MAX_AVERAGE_TORQUE"] # 减速器平均负载转矩允许最大值 r_avg_t = configs["TRANSMISSION"]["MAX_AVERAGE_TORQUE"] # 减速器平均负载转矩允许最大值
insert_logdb("INFO", "current", f"get_configs: 机型文件版本 {config_file}_{version}") insert_logdb("INFO", "current", f"get_configs: 机型文件版本 {config_file}_{version}")
insert_logdb("INFO", "current", f"get_configs: 减速比 {r_rrs}") insert_logdb("INFO", "current", f"get_configs: 减速比 {r_rrs}")
insert_logdb("INFO", "current", f"get_configs: 额定电流 {m_rcs}") insert_logdb("INFO", "current", f"get_configs: 额定转矩 {m_rts}")
return m_rcs, m_max_cs, m_stall_cs, m_rts, m_max_ts, m_r_rpms, m_max_rpms, m_tcs, r_rrs, r_max_sst, r_max_t, sc, r_rts, r_r_rpms, r_life_cycle, r_avg_t insert_logdb("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:
insert_logdb("ERROR", "current", f"get_config: 无法打开 {config_file},或获取配置文件参数错误 {Err}")
w2t(f"无法打开 {config_file},或者使用了错误的机型配置文件,需检查\n", color="red", desc="OpenFileError")
def main(): def main():
@ -418,13 +417,13 @@ def main():
data_files, config_file = initialization(path, w2t, insert_logdb) data_files, config_file = initialization(path, w2t, insert_logdb)
params = get_configs(config_file, w2t, insert_logdb) params = get_configs(config_file, w2t, insert_logdb)
rcs, rrs = params[0], params[8] rts, rrs = params[4], params[1]
if sub == "max": if sub == "max":
current_max(data_files, rcs, trq, w2t, insert_logdb) current_max(data_files, rts, trq, w2t, insert_logdb)
elif sub == "avg": elif sub == "avg":
current_avg(data_files, rcs, trqh, w2t, insert_logdb) current_avg(data_files, rts, trqh, w2t, insert_logdb)
elif sub == "cycle": elif sub == "cycle":
current_cycle(data_files, vel, trq, trqh, sensor, rrs, rcs, params, w2t, insert_logdb) current_cycle(data_files, vel, trq, trqh, sensor, rrs, rts, params, w2t, insert_logdb)
w2t("-"*60 + "\n全部处理完毕\n") w2t("-"*60 + "\n全部处理完毕\n")
time_end = time.time() time_end = time.time()

View File

@ -1,6 +1,7 @@
import pandas import pandas
import csv import csv
import openpyxl import openpyxl
import chardet
from common import clibs from common import clibs
@ -9,7 +10,7 @@ def find_point(bof, step, margin, threshold, pos, data_file, flag, df, row, w2t)
# pos: used for debug # pos: used for debug
# flag: greater than or lower than # flag: greater than or lower than
row_target = None row_target = None
row_origin = df.index[-1] - margin + 1 row_origin = len(df) - margin + 1
if flag == "gt": if flag == "gt":
while 0 < row < row_origin: while 0 < row < row_origin:
value = float(df.iloc[row, 2]) value = float(df.iloc[row, 2])
@ -22,7 +23,7 @@ def find_point(bof, step, margin, threshold, pos, data_file, flag, df, row, w2t)
else: else:
if bof == "backward": if bof == "backward":
clibs.insert_logdb("ERROR", "wavelogger", f"find_point-gt: [{pos}] 在 {data_file} 中,无法正确识别数据,需要确认...") clibs.insert_logdb("ERROR", "wavelogger", f"find_point-gt: [{pos}] 在 {data_file} 中,无法正确识别数据,需要确认...")
w2t(f"[{pos}] 在 {data_file} 中,无法正确识别数据,需要确认...", "red", "DataError") w2t(f"[{pos}] 在 {data_file} 中,无法正确识别数据,需要确认...\n", "red", "DataError")
elif bof == "forward": elif bof == "forward":
row_target = row + margin # to end while loop in function `single_file_proc` row_target = row + margin # to end while loop in function `single_file_proc`
elif flag == "lt": elif flag == "lt":
@ -37,7 +38,7 @@ def find_point(bof, step, margin, threshold, pos, data_file, flag, df, row, w2t)
else: else:
if bof == "backward": if bof == "backward":
clibs.insert_logdb("ERROR", "wavelogger", f"find_point-lt: [{pos}] 在 {data_file} 中,无法正确识别数据,需要确认...") clibs.insert_logdb("ERROR", "wavelogger", f"find_point-lt: [{pos}] 在 {data_file} 中,无法正确识别数据,需要确认...")
w2t(f"[{pos}] 在 {data_file} 中,无法正确识别数据,需要确认...", "red", "DataError") w2t(f"[{pos}] 在 {data_file} 中,无法正确识别数据,需要确认...\n", "red", "DataError")
elif bof == "forward": elif bof == "forward":
row_target = row + margin # to end while loop in function `single_file_proc` row_target = row + margin # to end while loop in function `single_file_proc`
return row_target return row_target
@ -49,10 +50,14 @@ def get_cycle_info(data_file, step, margin, threshold, w2t):
# 1. 从最后读取数据无论是大于1还是小于1都舍弃找到相反的值的起始点 # 1. 从最后读取数据无论是大于1还是小于1都舍弃找到相反的值的起始点
# 2. 从起始点,继续往前寻找,找到与之数值相反的中间点 # 2. 从起始点,继续往前寻找,找到与之数值相反的中间点
# 3. 从中间点,继续往前寻找,找到与之数值相反的结束点,至此,得到了高低数值的时间区间以及一轮的周期时间 # 3. 从中间点,继续往前寻找,找到与之数值相反的结束点,至此,得到了高低数值的时间区间以及一轮的周期时间
csv_reader = csv.reader(open(data_file)) 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]) begin = int(next(csv_reader)[1])
df = pandas.read_csv(data_file, sep=",", encoding="gbk", skip_blank_lines=False, header=begin - 1, on_bad_lines="skip") df = pandas.read_csv(data_file, sep=",", encoding=encoding, skip_blank_lines=False, header=begin - 1, on_bad_lines="skip")
row = df.index[-1] - margin row = len(df) - margin
if float(df.iloc[row, 2]) < threshold: if float(df.iloc[row, 2]) < threshold:
row = find_point("backward", step, margin, threshold, "a1", data_file, "lt", df, row, w2t) row = find_point("backward", step, margin, threshold, "a1", data_file, "lt", df, row, w2t)
@ -73,7 +78,7 @@ def initialization(path, w2t):
for data_file in data_files: for data_file in data_files:
if not data_file.lower().endswith(".csv"): if not data_file.lower().endswith(".csv"):
clibs.insert_logdb("ERROR", "wavelogger", f"init: {data_file} 文件后缀错误,只允许 .csv 文件,需要确认!") clibs.insert_logdb("ERROR", "wavelogger", f"init: {data_file} 文件后缀错误,只允许 .csv 文件,需要确认!")
w2t(f"{data_file} 文件后缀错误,只允许 .csv 文件,需要确认!", "red", "FileTypeError") w2t(f"{data_file} 文件后缀错误,只允许 .csv 文件,需要确认!\n", "red", "FileTypeError")
return data_files return data_files
@ -88,7 +93,7 @@ def preparation(data_file, step, margin, threshold, wb, w2t):
def single_file_proc(ws, data_file, step, threshold, margin, data_length, df, cycle, w2t): def single_file_proc(ws, data_file, step, threshold, margin, data_length, df, cycle, w2t):
row, row_lt, row_gt, count, count_i, data = 1, 1, 1, 1, 1, {} row, row_lt, row_gt, count, count_i, data = 1, 1, 1, 1, 1, {}
row_max = df.index[-1] - margin row_max = len(df) - margin
while row < row_max: while row < row_max:
if count not in data.keys(): if count not in data.keys():
data[count] = [] data[count] = []
@ -98,9 +103,9 @@ def single_file_proc(ws, data_file, step, threshold, margin, data_length, df, cy
row_lt = find_point("forward", step, margin, threshold, "c"+str(row), data_file, "lt", df, row, w2t) row_lt = find_point("forward", step, margin, threshold, "c"+str(row), data_file, "lt", df, row, w2t)
start = int(row_gt + (row_lt - row_gt - data_length) / 2) start = int(row_gt + (row_lt - row_gt - data_length) / 2)
end = start + data_length end = start + data_length
value = df.iloc[start:end, 2].mean() + 3 * df.iloc[start:end, 2].std() value = df.iloc[start:end, 2].astype(float).mean() + 3 * df.iloc[start:end, 2].astype(float).std()
if value > 1: if value > 1:
msg = f"{data_file} 文件第 {count} 轮 第 {count_i} 个数据可能有问题,需人工手动确认,确认有问题可删除,无问题则保留" msg = f"{data_file} 文件第 {count} 轮 第 {count_i} 个数据可能有问题,需人工手动确认,确认有问题可删除,无问题则保留\n"
clibs.insert_logdb("WARNING", "wavelogger", msg) clibs.insert_logdb("WARNING", "wavelogger", msg)
w2t(msg, "orange") w2t(msg, "orange")
data[count].append(value) data[count].append(value)

View File

@ -0,0 +1,82 @@
import os.path
import matplotlib.pyplot as plt
import pandas
from matplotlib.widgets import Slider
from common import clibs
def initialization():
path, curves = None, None
try:
path = clibs.data_dd["path"]
curves = clibs.data_dd["curves"]
except Exception:
clibs.w2t("程序未开始运行,暂无数据可以展示......\n", "red")
return None, None
for curve in curves:
if not os.path.exists(f"{path}/{curve}.csv"):
clibs.w2t(f"{curve}曲线数据暂未生成,稍后再试......\n", "orange")
return None, None
return path, curves
def data_plot(path, curve):
titles = {"hw_joint_vel_feedback": "各关节最大速度曲线", "device_servo_trq_feedback": "各关节平均有效转矩曲线"}
ylabels = {"hw_joint_vel_feedback": "速度(rad/s)", "device_servo_trq_feedback": "转矩(Nm)"}
fig, axes = plt.subplots(figsize=(10, 4.5), dpi=100)
cols = [f"{curve}_{i}" for i in range(6)]
cols.insert(0, "time")
df = pandas.read_csv(f"{path}/{curve}.csv")
plt.plot(df[cols[1]], label="一轴")
plt.plot(df[cols[2]], label="二轴")
plt.plot(df[cols[3]], label="三轴")
plt.plot(df[cols[4]], label="四轴")
plt.plot(df[cols[5]], label="五轴")
plt.plot(df[cols[6]], label="六轴")
axes.set_title(titles[curve])
axes.set_ylabel(ylabels[curve])
axes.legend(loc="upper right")
slider_position = plt.axes((0.1, 0.01, 0.8, 0.05), facecolor="blue") # (left, bottom, width, height)
scrollbar = Slider(slider_position, 'Time', 1, int(len(df)), valstep=1)
def update(val):
pos = scrollbar.val
axes.set_xlim([pos, pos + 10])
fig.canvas.draw_idle()
scrollbar.on_changed(update)
fig.tight_layout(rect=(0, 0.02, 0.96, 1)) # tuple (left, bottom, right, top)
def main():
path, curves = initialization()
if not path or not curves:
return
for curve in curves:
data_plot(path, curve)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['figure.dpi'] = 100
plt.rcParams['font.size'] = 14
plt.rcParams['lines.marker'] = 'o'
plt.rcParams["figure.autolayout"] = True
plt.show()
# threads = [threading.Thread(target=data_plot, args=(path, curve)) for curve in curves]
# for t in threads:
# t.daemon = True
# t.start()
# for curve in curves:
# t = threading.Thread(target=data_plot, args=(path, curve))
# t.daemon = True
# t.start()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,271 @@
import json
import threading
import time
import pandas
import math
import csv
import numpy
from common import clibs
def initialization(path, hr, data_dirs, data_files, interval, curves, w2t):
def check_files():
nonlocal interval
if interval == "":
interval = 300
elif interval.isdigit():
if int(interval) < 300:
w2t(f"输入时间间隔 {interval} < 300使用默认时间间隔 300s ......\n", "orange")
interval = 300
else:
interval = int(interval)
else:
w2t(f"{interval} 不是有效的输入,时间间隔必须是一个大于 300 的正整数!\n", "red", "NotIntegerError")
if len(curves) == 0:
w2t("未查询到需要记录数据的曲线,至少选择一个!\n", "red", "CurveNameError")
if len(data_dirs) != 0 or len(data_files) != 1:
w2t("初始路径下不允许有文件夹,且初始路径下只能存在一个工程文件 —— *.zip确认后重新运行\n", "red", "InitFileError")
if not data_files[0].endswith(".zip"):
w2t(f"{data_files[0]} 不是一个有效的工程文件,需确认!\n", "red", "ProjectFileError")
return data_files[0], interval
def get_configs():
robot_type, records = None, None
try:
msg_id, state = hr.execution("controller.get_params")
records = hr.get_from_id(msg_id, state)
except Exception:
w2t("网络不可达需要先连接xCore\n", "red", "NetworkError")
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 = 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:
clibs.insert_logdb("ERROR", "current", f"get_config: 无法打开 {local_file},获取配置文件参数错误 {Err}")
w2t(f"无法打开 {local_file}\n", color="red", desc="OpenFileError")
# 最大角速度,额定电流,减速比,额定转速
version = configs["VERSION"]
m_avs = configs["MOTION"]["JOINT_MAX_SPEED"]
m_rts = configs["MOTOR"]["RATED_TORQUE"] # 电机额定转矩rt for rated torque
m_tcs = [1, 1, 1, 1, 1, 1] # 电机转矩常数tc for torque constant
m_rcs = []
for i in range(len(m_tcs)):
m_rcs.append(m_rts[i] / m_tcs[i]) # 电机额定电流rc for rated current
clibs.insert_logdb("INFO", "do_brake", f"get_configs: 机型文件版本 {robot_type}_{version}")
clibs.insert_logdb("INFO", "do_brake", f"get_configs: 各关节角速度 {m_avs}")
clibs.insert_logdb("INFO", "do_brake", f"get_configs: 各关节额定电流 {m_rcs}")
return m_avs, m_rcs
prj_file, interval = check_files()
avs, rcs = get_configs()
params = {
"prj_file": prj_file,
"interval": interval,
"avs": avs,
"rcs": rcs,
}
return params
def change_curve_state(hr, curves, stat_1, stat_2):
display_pdo_params = [{"name": name, "channel": chl} for name in curves for chl in range(6)]
hr.execution("diagnosis.open", open=stat_1, display_open=stat_2, overrun=True, turn_area=True, delay_motion=False)
hr.execution("diagnosis.set_params", display_pdo_params=display_pdo_params, frequency=50, version="1.4.1")
def run_rl(path, params, curves, hr, md, w2t):
prj_file, interval = params["prj_file"], params["interval"]
# 1. 关闭诊断曲线,触发软急停,并解除,目的是让可能正在运行着的机器停下来,切手动模式并下电
change_curve_state(hr, curves, False, False)
md.r_soft_estop(0)
md.r_soft_estop(1)
md.r_clear_alarm()
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"
hr.execution("overview.reload", prj_path=prj_path, tasks=["factory"])
hr.execution("rl_task.pp_to_main", tasks=["factory"])
hr.execution("state.switch_auto")
hr.execution("state.switch_motor_on")
# 3. 开始运行程序
hr.execution("rl_task.set_run_params", loop_mode=True, override=1.0)
hr.execution("rl_task.run", tasks=["factory"])
t_start = time.time()
while True:
if md.read_ready_to_go() == 1:
md.write_act(True)
break
else:
if (time.time() - t_start) > 3:
w2t("3s 内未收到机器人的运行信号需要确认RL程序编写正确并正常执行...\n", "red", "ReadySignalTimeoutError")
else:
time.sleep(1)
# 4. 获取初始数据,周期时间,首次的各轴平均电流值,打开诊断曲线,并执行采集
time.sleep(10) # 等待 RL 程序中 scenario_time 初始化
t_start = time.time()
while True:
scenario_time = float(f"{float(md.read_scenario_time()):.2f}")
if scenario_time != 0:
w2t(f"耐久工程的周期时间:{scenario_time}s | 单轮次执行时间:{scenario_time+interval}\n")
break
else:
time.sleep(1)
if (time.time() - t_start) > 300:
w2t(f"300s 内未收到耐久工程的周期时间需要确认RL程序和工具通信交互是否正常执行...\n", "red", "GetScenarioTimeError")
# 6. 准备数据保存文件
for curve in curves:
with open(f"{path}/{curve}.csv", mode="a+", newline="") as f_csv:
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:
if not clibs.running:
w2t("后台数据清零完成,现在可以重新运行之前停止的程序。", "red")
exit()
this_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
next_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()+scenario_time+interval+1))
w2t(f"[{this_time}] 当前次数:{count:09d} | 预计下次数据更新时间:{next_time}\n", "#008B8B")
count += 1
# 固定间隔,更新一次数据,打开曲线,获取周期内电流,关闭曲线
time.sleep(interval)
change_curve_state(hr, curves, True, True)
time.sleep(scenario_time)
end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()-scenario_time))
change_curve_state(hr, curves, False, False)
# 保留数据并处理输出
gen_results(params, curves, start_time, end_time, w2t)
def gen_results(params, curves, start_time, end_time, w2t):
try:
clibs.lock.acquire(True)
clibs.cursor.execute(f"select content from logs where time between '{start_time}' and '{end_time}' and content like '%diagnosis.result%' order by id asc")
records = clibs.cursor.fetchall()
finally:
clibs.lock.release()
data_proc(records, params, curves, w2t)
def data_proc(records, params, curves, w2t):
for curve in curves:
if curve == "device_servo_trq_feedback":
# proc_device_servo_trq_feedback(records, params, w2t)
t = threading.Thread(target=proc_device_servo_trq_feedback, args=(records, params, w2t))
t.daemon = True
t.start()
elif curve == "hw_joint_vel_feedback":
# proc_hw_joint_vel_feedback(records, params, w2t)
t = threading.Thread(target=proc_hw_joint_vel_feedback, args=(records, params, w2t))
t.daemon = True
t.start()
def proc_device_servo_trq_feedback(records, params, w2t):
d_trq, rcs, results = [[], [], [], [], [], []], params["rcs"], [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(df.apply(lambda x: numpy.power((rcs[axis] * float(x.iloc[0]) / 1000), 2)).sum() / len(df))
results.append(_)
path = "/".join(params["prj_file"].split("/")[:-1])
with open(f"{path}/device_servo_trq_feedback.csv", mode="a+", newline="") as f_csv:
csv_writer = csv.writer(f_csv)
csv_writer.writerow(results)
def proc_hw_joint_vel_feedback(records, params, w2t):
d_trq, rcs, results = [[], [], [], [], [], []], params["rcs"], [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}/hw_joint_vel_feedback.csv", mode="a+", newline="") as f_csv:
csv_writer = csv.writer(f_csv)
csv_writer.writerow(results)
def detect_db_size():
@clibs.db_lock
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(1)
def main():
t = threading.Thread(target=detect_db_size)
t.daemon = True
t.start()
path = clibs.data_dd["path"]
interval = clibs.data_dd["interval"].strip()
curves = clibs.data_dd["curves"]
hr = clibs.c_hr
md = clibs.c_md
w2t = clibs.w2t
data_dirs, data_files = clibs.traversal_files(path, w2t)
params = initialization(path, hr, data_dirs, data_files, interval, curves, w2t)
prj_file = params["prj_file"]
clibs.c_pd.push_prj_to_server(prj_file)
try:
run_rl(path, params, curves, hr, md, w2t)
except Exception as Err:
w2t(f"工厂耐久程序执行过程中出现异常,{Err}\n", "red")
change_curve_state(hr, curves, False, False)
if __name__ == "__main__":
main()

View File

@ -22,6 +22,13 @@
## 三、注意事项 ## 三、注意事项
1. 仅适用于 xCore 2.3.0.7 及以上的版本
2. 单轴电机电流数据处理,至少需要三个完整周期
3. 执行制动测试/电机电流采集/耐久测试的时候,执行过程中停止,再次运行需要等待一分钟左右,输出框会有提示
4. 其他使用方法和之前工具一致
> **需要使用 assets/files/projects/ 下的工程,寄存器文件以及配置文件等!!!**
## 四、发版记录 ## 四、发版记录
## 五、其他 ## 五、其他
@ -31,13 +38,24 @@
打包时,只需要修改 clibs.py 中的 PREFIX 即可,调试时再修改回来 打包时,只需要修改 clibs.py 中的 PREFIX 即可,调试时再修改回来
``` ```
pyinstaller --noconfirm --onedir --windowed --optimize 2 --contents-directory . --upx-dir "D:/Syncthing/common/A_Program/upx-4.2.4-win64/" --add-data "../.venv/Lib/site-packages/customtkinter;customtkinter/" --add-data "../assets:assets" --version-file ../assets/files/version/file_version_info.txt -i ../assets/media/icon.ico ../code/aio.py -p ../code/data_process/brake.py -p ../code/data_process/iso.py -p ../code/data_process/current.py -p ../code/data_process/wavelogger.py -p ../code/commons/clibs.py -p ../code/commons/openapi.py -p ../code/automatic_test/do_current.py -p ../code/automatic_test/do_brake.py --exclude-module=scipy --exclude-module=matplotlib pyinstaller --noconfirm --onedir --windowed --optimize 2 --contents-directory . --upx-dir "D:/Syncthing/common/A_Program/upx-4.2.4-win64/" --add-data "../.venv/Lib/site-packages/customtkinter;customtkinter/" --add-data "../assets:assets" --version-file ../assets/files/version/file_version_info.txt -i ../assets/media/icon.ico ../code/aio.py -p ../code/common/clibs.py -p ../code/commom/openapi.py -p ../code/data_process/brake.py -p ../code/data_process/iso.py -p ../code/data_process/current.py -p ../code/data_process/wavelogger.py -p ../code/automatic_test/do_current.py -p ../code/automatic_test/do_brake.py -p ../code/durable_docs/factory_test.py -p ../code/durable_docs/create_plot.py --exclude-module=scipy
``` ```
### 2. tabview 组件字体修改 ### 2. tabview 组件字体修改
customtkinter的tabview组件不支持修改字体大小可以参考 [Changing Font of a Tabview](https://github.com/TomSchimansky/CustomTkinter/issues/2296) 进行手动修改源码实现: customtkinter的tabview组件不支持修改字体大小解决方法可参考如下
Method 1可以参考 [Changing Font of a Tabview](https://github.com/TomSchimansky/CustomTkinter/issues/2296) 进行手动修改源码实现:
a. 运行 `pip show customtkinter`,获取到库的路径 a. 运行 `pip show customtkinter`,获取到库的路径
b. 修改.../windows/widgets/ctk_tabview.py b. 修改.../windows/widgets/ctk_tabview.py
c. 增加 from .font.ctk_font import CTkFont c. 增加 from .font.ctk_font import CTkFont
d. 在大概 78 行的位置,增加 font=CTkFont(family="Consolas", size=18, weight='bold') d. 在大概 78 行的位置,增加 font=CTkFont(family="Consolas", size=18, weight='bold')
Method 2
self.tabview_bottom._segmented_button.configure(font=ctk.CTkFont(family="Consolas", size=18, weight="bold"))
### 3. scroll frame 不支持修改高度和宽度
https://github.com/TomSchimansky/CustomTkinter/pull/1765/files