class AudioFlashTool:
def __init__(self, root):
# 处理打包环境和开发环境
if getattr(sys, 'frozen', False):
# 打包环境:资源位于临时目录(sys._MEIPASS)
self.root_dir = sys._MEIPASS
self.log_path = os.path.dirname(sys.executable)
else:
# 开发环境:当前脚本所在目录
self.root_dir = os.path.dirname(os.path.abspath(__file__))
self.log_path = os.path.dirname(os.path.abspath(__file__))
# 设置资源路径
self.config_file = os.path.join(self.root_dir, "config.json")
self.ico_file = os.path.join(self.root_dir, "headphones.ico")
self.devices_dir = os.path.join(self.root_dir, "devices_dir")
self.flash_exe = os.path.join(self.root_dir, "HiAudioUploadTool.exe")
self.export_exe = os.path.join(self.root_dir, "HiAudioGetLogTool.exe")
self.puffer_flash_exe = os.path.join(self.root_dir, "puffer_upgrade_tool.exe")
# 设置日志路径
self.log_dir = os.path.join(self.log_path, r"logs")
self.tool_logs_dir = os.path.join(self.log_path, r"logs\tool_logs")
self.audio_logs_dir = os.path.join(self.log_path, r"logs\audio_logs")
# 设置编码格式
self.port_encoding = "utf-8"
self.exe_encoding = "gbk"
self.root = root
self.root.title("音频设备刷机工具v2.0 作者:罗俊l60072836")
# 设置初始窗口尺寸
self.window_width = 1100
self.window_height = 670
# 不允许缩小\放大窗口且居中显示
self.root.resizable(False, False)
center_window(root, self.window_width, self.window_height)
# 初始化设备选择状态变量
self.device_selection = {
"charging_box": tk.BooleanVar(value=False),
"left_ear": tk.BooleanVar(value=False),
"right_ear": tk.BooleanVar(value=False)
}
# 创建主标签页
self.notebook = ttk.Notebook(root)
self.notebook.pack(fill=BOTH, expand=True, padx=10, pady=10)
# 创建主工作区标签页
self.main_tab = ttk.Frame(self.notebook)
self.notebook.add(self.main_tab, text="工具页面")
self.setup_main_tab()
# 创建日志标签页
self.log_tab = ttk.Frame(self.notebook)
self.notebook.add(self.log_tab, text="详细日志")
self.setup_log_tab()
# 初始化设备信息
self.device_info = {
"charging_box": {"SN": "CB-2023-001", "内部版本": "v1.2.5", "外部版本": "v1.0", "MCU": "STM32F401"},
"left_ear": {"SN": "LE-2023-001", "内部版本": "v1.1.8", "外部版本": "v1.0", "MCU": "nRF52832"},
"right_ear": {"SN": "RE-2023-001", "内部版本": "v1.1.8", "外部版本": "v1.0", "MCU": "nRF52832"}
}
# 初始化设备状态
self.device_status = {
"charging_box": False,
"left_ear": False,
"right_ear": False
}
def setup_main_tab(self):
# 主内容区域 - 直接开始布局内容
main_frame = ttk.Frame(self.main_tab)
main_frame.pack(fill=BOTH, expand=True, padx=10, pady=5)
# 创建容器确保左右面板等高
equal_height_container = ttk.Frame(main_frame)
equal_height_container.pack(fill=BOTH, expand=True)
# 左面板
left_panel = ttk.Frame(equal_height_container)
left_panel.pack(side=LEFT, fill=BOTH, padx=(0, 10))
# 右面板
right_panel = ttk.Frame(equal_height_container)
right_panel.pack(side=RIGHT, fill=BOTH, expand=True)
# ========== 左面板内容 ==========
# 设备设置
conn_frame = ttk.Labelframe(left_panel, text="设备设置", padding=10)
conn_frame.pack(fill=X, pady=(0, 10))
# 串口号选择
port_row = ttk.Frame(conn_frame)
port_row.pack(fill=X, pady=5)
ttk.Label(port_row, text="串口选择:").pack(side=LEFT, padx=(0, 5))
self.port_combobox = ttk.Combobox(port_row, width=20)
self.port_combobox.pack(side=LEFT, padx=(0, 5), fill=X, expand=True)
refresh_btn = ttk.Button(
port_row,
text="刷新",
command=self.refresh_ports,
width=4
)
refresh_btn.pack(side=LEFT)
# 设备型号选择
model_row = ttk.Frame(conn_frame)
model_row.pack(fill=X, pady=5)
ttk.Label(model_row, text="设备型号:").pack(side=LEFT, padx=(0, 5))
self.model_combobox = ttk.Combobox(
model_row,
width=20,
values=["TWS-1000", "TWS-2000 Pro", "TWS-3000 Max"]
)
self.model_combobox.pack(side=LEFT, fill=X, expand=True)
self.model_combobox.set("TWS-2000 Pro")
# 设备名称显示
name_row = ttk.Frame(conn_frame)
name_row.pack(fill=X, pady=5)
ttk.Label(name_row, text="设备名称:").pack(side=LEFT, padx=(0, 5))
self.device_name = ttk.Label(name_row, text="TWS-2000 Pro")
self.device_name.pack(side=LEFT)
# 设备版本选择
version_row = ttk.Frame(conn_frame)
version_row.pack(fill=X, pady=5)
ttk.Label(version_row, text="设备版本:").pack(side=LEFT, padx=(0, 5))
self.version_combobox = ttk.Combobox(
version_row,
width=20,
values=["v1.0.0", "v1.1.0", "v1.2.0", "v2.0.0"]
)
self.version_combobox.pack(side=LEFT, fill=X, expand=True)
self.version_combobox.set("v2.0.0")
# 充电盒版本文件选择
box_file_row = ttk.Frame(conn_frame)
box_file_row.pack(fill=X, pady=5)
ttk.Label(box_file_row, text="充电盒版本文件:").pack(side=LEFT, padx=(0, 5))
self.box_file_entry = ttk.Entry(box_file_row, width=18)
self.box_file_entry.pack(side=LEFT, fill=X, expand=True, padx=(0, 5))
self.box_file_entry.insert(0, r"内部文件\Conch-T010\5.0.0.126\box_end_userconch_mu0303_12.hi.dfu")
box_browse_btn = ttk.Button(
box_file_row,
text="浏览",
command=self.box_browse_file,
bootstyle=SECONDARY,
width=4
)
box_browse_btn.pack(side=LEFT)
# 耳机版本文件选择
ears_file_row = ttk.Frame(conn_frame)
ears_file_row.pack(fill=X, pady=5)
ttk.Label(ears_file_row, text="耳机版本文件:").pack(side=LEFT, padx=(0, 5))
self.ears_file_entry = ttk.Entry(ears_file_row, width=18)
self.ears_file_entry.pack(side=LEFT, fill=X, expand=True, padx=(0, 5))
self.ears_file_entry.insert(0, r"内部文件\Conch-T010\5.0.0.126\box_end_userconch_mu0303_12.hi.dfu")
ears_browse_btn = ttk.Button(
ears_file_row,
text="浏览",
command=self.box_browse_file,
bootstyle=SECONDARY,
width=4
)
ears_browse_btn.pack(side=LEFT)
# 设备版本类型选择
type_row = ttk.Frame(conn_frame)
type_row.pack(fill=X, pady=5)
ttk.Label(type_row, text="版本类型:").pack(side=LEFT, padx=(0, 5))
self.type_combobox = ttk.Combobox(
type_row,
width=20,
values=["正式版", "测试版", "调试版"]
)
self.type_combobox.pack(side=LEFT, fill=X, expand=True)
self.type_combobox.set("正式版")
# 设备选择(使用勾选框)
device_frame = ttk.Labelframe(left_panel, text="设备选择", padding=10)
device_frame.pack(fill=X, pady=(0, 10))
# 创建容器放置三个勾选框在同一行
device_selection_row = ttk.Frame(device_frame)
device_selection_row.pack(fill=X, pady=5)
# 充电盒勾选框
ttk.Checkbutton(
device_selection_row,
text="充电盒",
variable=self.device_selection["charging_box"]
).pack(side=LEFT, padx=10)
# 左耳勾选框
ttk.Checkbutton(
device_selection_row,
text="左耳",
variable=self.device_selection["left_ear"]
).pack(side=LEFT, padx=10)
# 右耳勾选框
ttk.Checkbutton(
device_selection_row,
text="右耳",
variable=self.device_selection["right_ear"]
).pack(side=LEFT, padx=10)
# 功能选择按钮
func_frame = ttk.Labelframe(left_panel, text="功能选择按钮", padding=10)
func_frame.pack(fill=X)
ttk.Button(
func_frame,
text="开始连接设备",
command=self.connect_device,
bootstyle=SUCCESS,
width=15
).pack(fill=X, pady=5)
ttk.Button(
func_frame,
text="执行设备刷机",
command=self.flash_device,
bootstyle=PRIMARY,
width=15
).pack(fill=X, pady=5)
ttk.Button(
func_frame,
text="导出设备日志",
command=self.export_logs,
bootstyle=INFO,
width=15
).pack(fill=X, pady=5)
ttk.Button(
func_frame,
text="日志目录查看",
command=self.view_log_dir,
bootstyle=SECONDARY,
width=15
).pack(fill=X, pady=5)
# ========== 右面板内容 ==========
# 设备信息展示
info_frame = ttk.Labelframe(right_panel, text="设备信息", padding=10)
info_frame.pack(fill=X, pady=(0, 10))
# 设备信息表格
columns = ("部件", "SN信息", "内部版本", "外部版本", "MCU信息")
# 创建样式并增大行高
style = ttk.Style()
style.configure("Treeview", rowheight=35, padding=(0, 5, 0, 5)) # 增加行高50%(默认约为25)
self.device_info_tree = ttk.Treeview(
info_frame,
columns=columns,
show="headings",
height=3,
selectmode="browse",
style="Treeview" # 应用新样式
)
# 设置列标题和宽度
for col in columns:
self.device_info_tree.heading(col, text=col)
self.device_info_tree.column(col, width=100, anchor=CENTER)
# 添加设备信息
self.device_info_tree.insert("", "end", values=("充电盒", "", "", "", ""), tags=("charging_box",))
self.device_info_tree.insert("", "end", values=("左耳", "", "", "", ""), tags=("left_ear",))
self.device_info_tree.insert("", "end", values=("右耳", "", "", "", ""), tags=("right_ear",))
self.device_info_tree.pack(fill=X)
# 添加右键菜单用于复制
self.context_menu = tk.Menu(self.root, tearoff=1)
self.context_menu.add_command(label="复制", command=self.copy_cell_value)
self.device_info_tree.bind("<Button-3>", self.show_context_menu)
# 结果展示区域
result_frame = ttk.Labelframe(right_panel, text="结果展示", padding=10)
result_frame.pack(fill=BOTH, expand=True, pady=(0, 10))
self.result_text = tk.Text(result_frame, height=6)
self.result_text.pack(fill=BOTH, expand=True)
self.result_text.insert(END, ">>> 系统启动成功\n")
self.result_text.insert(END, ">>> 请选择串口并连接设备\n")
self.result_text.config(state=DISABLED)
# 清空按钮
clear_btn_frame = ttk.Frame(result_frame)
clear_btn_frame.pack(fill=X, pady=(5, 0))
ttk.Button(
clear_btn_frame,
text="清空",
command=self.clear_results,
bootstyle=OUTLINE,
width=5
).pack(side=RIGHT)
# 指令发送区域
cmd_frame = ttk.Labelframe(right_panel, text="指令发送", padding=10)
cmd_frame.pack(fill=X)
# 创建水平容器用于指令输入和按钮
cmd_container = ttk.Frame(cmd_frame)
cmd_container.pack(fill=X, pady=5)
# 命令输入框(左侧)
self.cmd_entry = ttk.Entry(cmd_container)
self.cmd_entry.pack(side=LEFT, fill=X, expand=True, padx=(0, 10))
self.cmd_entry.insert(0, "AT+INFO?")
# 创建按钮容器(右侧)
btn_container = ttk.Frame(cmd_container)
btn_container.pack(side=RIGHT)
# 发送和清空按钮(在按钮容器内从右向左排列)
ttk.Button(
btn_container,
text="清空",
command=self.clear_command,
bootstyle=OUTLINE,
width=5
).pack(side=RIGHT, padx=(5, 0))
ttk.Button(
btn_container,
text="发送",
command=self.send_command,
bootstyle=PRIMARY,
width=5
).pack(side=RIGHT)
def setup_log_tab(self):
# 日志文本区域
log_frame = ttk.Frame(self.log_tab)
log_frame.pack(fill=BOTH, expand=True, padx=10, pady=10)
self.log_text = tk.Text(log_frame, wrap=WORD)
self.log_text.pack(fill=BOTH, expand=True)
# 添加滚动条
scrollbar = ttk.Scrollbar(log_frame, command=self.log_text.yview)
scrollbar.pack(side=RIGHT, fill=Y)
self.log_text.config(yscrollcommand=scrollbar.set)
# 初始日志内容
self.log_text.insert(END, "=" * 50 + " 系统日志 " + "=" * 50 + "\n")
self.log_text.insert(END, "[2023-11-15 14:25:10] 应用程序启动\n")
self.log_text.insert(END, "[2023-11-15 14:25:12] 初始化串口模块\n")
self.log_text.insert(END, "[2023-11-15 14:25:15] 加载设备配置文件\n")
self.log_text.insert(END, "[2023-11-15 14:25:18] 初始化用户界面\n")
self.log_text.insert(END, "[2023-11-15 14:25:20] 准备就绪\n")
def refresh_ports(self):
"""刷新可用串口列表"""
ports = [f"COM{i}" for i in range(1, 5)]
self.port_combobox["values"] = ports
if ports:
self.port_combobox.set(ports[0])
self.append_result(f"发现串口: {', '.join(ports)}")
else:
self.port_combobox.set("")
self.append_result("未发现可用串口")
def box_browse_file(self):
"""浏览文件"""
self.append_result("打开文件选择对话框")
self.box_file_entry.delete(0, END)
self.box_file_entry.insert(0, "firmware_v2.0.0.bin")
def ears_browse_file(self):
"""浏览文件"""
self.append_result("打开文件选择对话框")
self.ears_file_entry.delete(0, END)
self.ears_file_entry.insert(0, "firmware_v2.0.0.bin")
def connect_device(self):
"""连接设备"""
port = self.port_combobox.get()
if not port:
self.append_result("错误: 请选择串口")
return
threading.Thread(target=self._connect_device_thread, daemon=True).start()
def _connect_device_thread(self):
"""设备连接线程"""
port = self.port_combobox.get()
self.append_result(f"正在连接设备 ({port})...")
self.root.update()
time.sleep(1.5)
# 更新设备信息表格
for item in self.device_info_tree.get_children():
device = self.device_info_tree.item(item, "tags")[0]
sn = self.device_info[device]["SN"]
internal_ver = self.device_info[device]["内部版本"]
external_ver = self.device_info[device]["外部版本"]
mcu = self.device_info[device]["MCU"]
status = "已连接" if random.random() > 0.3 else "未连接"
self.device_status[device] = (status == "已连接")
self.device_info_tree.item(item, values=(
self.device_info_tree.item(item, "values")[0],
f"{sn} ({status})",
internal_ver,
external_ver,
mcu
))
self.append_result("设备连接成功!")
def flash_device(self):
"""执行设备刷机"""
connected_devices = [device for device, connected in self.device_status.items() if connected]
selected_devices = [device for device, selected in self.device_selection.items() if selected.get()]
if not connected_devices:
self.append_result("错误: 没有已连接的设备")
return
if not selected_devices:
self.append_result("错误: 请选择至少一个设备")
return
threading.Thread(target=self._flash_device_thread, args=(selected_devices,), daemon=True).start()
def _flash_device_thread(self, devices):
"""设备刷机线程"""
self.append_result(f"开始刷机: {', '.join(devices)}")
self.append_result("正在擦除旧固件...")
time.sleep(1.5)
self.append_result("正在写入新固件...")
for i in range(1, 6):
time.sleep(0.5)
self.append_result(f"进度: {i * 20}%")
self.append_result("固件校验中...")
time.sleep(1)
success = random.random() > 0.2
if success:
self.append_result("刷机成功!")
else:
self.append_result("刷机失败! 请检查连接并重试")
def export_logs(self):
"""导出设备日志"""
self.append_result("正在导出设备日志...")
time.sleep(1)
self.append_result("设备日志已导出到 logs/device_logs_20231115.log")
def view_log_dir(self):
"""查看日志目录"""
self.append_result("打开日志目录: logs/")
def append_result(self, message):
"""在结果区域添加消息"""
self.result_text.config(state=NORMAL)
self.result_text.insert(END, f">>> {message}\n")
self.result_text.see(END)
self.result_text.config(state=DISABLED)
self.log_text.insert(END, f"[操作] {message}\n")
self.log_text.see(END)
def clear_results(self):
"""清空结果区域"""
self.result_text.config(state=NORMAL)
self.result_text.delete(1.0, END)
self.result_text.insert(END, ">>> 结果区域已清空\n")
self.result_text.config(state=DISABLED)
self.log_text.insert(END, "[操作] 清空结果区域\n")
self.log_text.see(END)
def send_command(self):
"""发送命令"""
cmd = self.cmd_entry.get()
if not cmd:
self.append_result("错误: 请输入命令")
return
self.append_result(f"发送命令: {cmd}")
responses = {
"AT+INFO?": "设备信息: TWS-2000 Pro, FW v2.0.0, 电池85%",
"AT+VERSION": "固件版本: v2.0.0",
"AT+RESET": "设备正在重启...",
"AT+ERASE": "开始擦除固件...",
"AT+FLASH": "开始刷写固件..."
}
response = responses.get(cmd, f"未知命令: {cmd}")
self.root.after(800, lambda: self.append_result(f"响应: {response}"))
def clear_command(self):
"""清空命令输入框"""
self.cmd_entry.delete(0, END)
def show_context_menu(self, event):
"""显示右键菜单"""
region = self.device_info_tree.identify("region", event.x, event.y)
if region == "cell":
self.context_menu.post(event.x_root, event.y_root)
def copy_cell_value(self):
"""复制选中单元格的值"""
selected_item = self.device_info_tree.selection()
if not selected_item:
return
column = self.device_info_tree.identify_column(self.root.winfo_pointerx() - self.root.winfo_rootx())
if not column:
return
col_index = int(column.replace("#", "")) - 1
item_values = self.device_info_tree.item(selected_item, "values")
if col_index < len(item_values):
cell_value = item_values[col_index]
pyperclip.copy(cell_value)
self.append_result(f"已复制: {cell_value}")
if __name__ == "__main__":
root = ttkbootstrap.Window(themename="minty")
tool = AudioFlashTool(root)
root.iconbitmap(tool.ico_file)
root.after(100, tool.refresh_ports) # 初始刷新串口
root.mainloop()
去除复制功能,直接通过界面双击信息文本内容实现复制,优化后的代码全部输出出来,不改变其他类名称、方法名称、变量名称
最新发布