#!/usr/bin/env python # -*- coding: utf-8 -*- """ 电动扳手自动拧紧界面程序 支持多螺栓自动拧紧流程 """ import tkinter as tk from tkinter import ttk, messagebox import json import threading import time from datetime import datetime from wrench_controller import WrenchController class WrenchGUI: """电动扳手图形界面""" def __init__(self, root): self.root = root self.root.title("电动扳手自动拧紧系统") self.root.geometry("900x750") # 数据 self.work_order = None self.current_bolt_index = 0 self.wrench = None self.is_running = False self.thread = None self.test_mode = False # 创建界面 self._create_widgets() # 加载工单数据 self.load_work_order() # 检查测试模式 self.check_test_mode() def _create_widgets(self): """创建界面组件""" # 标题 title_frame = tk.Frame(self.root, bg="#2c3e50", height=60) title_frame.pack(fill=tk.X) title_frame.pack_propagate(False) title_label = tk.Label( title_frame, text="电动扳手自动拧紧系统", font=("微软雅黑", 20, "bold"), fg="white", bg="#2c3e50" ) title_label.pack(pady=15) # 测试模式指示器 self.test_mode_label = tk.Label( title_frame, text="", font=("微软雅黑", 10, "bold"), fg="#f39c12", bg="#2c3e50" ) self.test_mode_label.place(relx=1.0, rely=0.5, anchor=tk.E, x=-20) # 工单信息区域 info_frame = tk.LabelFrame(self.root, text="工单信息", font=("微软雅黑", 12), padx=10, pady=10) info_frame.pack(fill=tk.X, padx=10, pady=10) self.order_id_label = tk.Label(info_frame, text="工单号: --", font=("微软雅黑", 10)) self.order_id_label.grid(row=0, column=0, sticky=tk.W, padx=5, pady=2) self.product_label = tk.Label(info_frame, text="产品: --", font=("微软雅黑", 10)) self.product_label.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2) self.station_label = tk.Label(info_frame, text="工位: --", font=("微软雅黑", 10)) self.station_label.grid(row=1, column=0, sticky=tk.W, padx=5, pady=2) self.operator_label = tk.Label(info_frame, text="操作员: --", font=("微软雅黑", 10)) self.operator_label.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2) # 当前螺栓信息 current_frame = tk.LabelFrame(self.root, text="当前螺栓", font=("微软雅黑", 12), padx=10, pady=10) current_frame.pack(fill=tk.X, padx=10, pady=10) self.current_bolt_label = tk.Label( current_frame, text="等待开始...", font=("微软雅黑", 16, "bold"), fg="#2c3e50" ) self.current_bolt_label.pack(pady=5) self.current_torque_label = tk.Label( current_frame, text="目标扭矩: -- Nm", font=("微软雅黑", 12) ) self.current_torque_label.pack(pady=2) self.current_status_label = tk.Label( current_frame, text="状态: 待机", font=("微软雅黑", 12), fg="#7f8c8d" ) self.current_status_label.pack(pady=2) # 进度条 progress_frame = tk.Frame(self.root, padx=10, pady=5) progress_frame.pack(fill=tk.X, padx=10) self.progress_label = tk.Label(progress_frame, text="总进度: 0/0", font=("微软雅黑", 10)) self.progress_label.pack(anchor=tk.W) self.progress_bar = ttk.Progressbar(progress_frame, mode='determinate', length=400) self.progress_bar.pack(fill=tk.X, pady=5) # 螺栓列表 list_frame = tk.LabelFrame(self.root, text="螺栓列表", font=("微软雅黑", 12), padx=10, pady=10) list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 创建表格 columns = ("序号", "名称", "扭矩(Nm)", "状态", "实际扭矩", "时间") self.tree = ttk.Treeview(list_frame, columns=columns, show="headings", height=6) # 设置列 self.tree.heading("序号", text="序号") self.tree.heading("名称", text="名称") self.tree.heading("扭矩(Nm)", text="扭矩(Nm)") self.tree.heading("状态", text="状态") self.tree.heading("实际扭矩", text="实际扭矩(Nm)") self.tree.heading("时间", text="完成时间") self.tree.column("序号", width=60, anchor=tk.CENTER) self.tree.column("名称", width=150, anchor=tk.W) self.tree.column("扭矩(Nm)", width=100, anchor=tk.CENTER) self.tree.column("状态", width=100, anchor=tk.CENTER) self.tree.column("实际扭矩", width=120, anchor=tk.CENTER) self.tree.column("时间", width=150, anchor=tk.CENTER) # 滚动条 scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview) self.tree.configure(yscrollcommand=scrollbar.set) self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 配置标签颜色 self.tree.tag_configure("pending", foreground="#7f8c8d") self.tree.tag_configure("running", foreground="#3498db", font=("微软雅黑", 10, "bold")) self.tree.tag_configure("success", foreground="#27ae60", font=("微软雅黑", 10, "bold")) self.tree.tag_configure("failed", foreground="#e74c3c") # 控制按钮 button_frame = tk.Frame(self.root, padx=10, pady=10) button_frame.pack(fill=tk.X) self.start_button = tk.Button( button_frame, text="开始拧紧", font=("微软雅黑", 12, "bold"), bg="#27ae60", fg="white", width=15, height=1, cursor="hand2", command=self.start_process ) self.start_button.pack(side=tk.LEFT, padx=5) self.stop_button = tk.Button( button_frame, text="停止", font=("微软雅黑", 12, "bold"), bg="#e74c3c", fg="white", width=15, height=1, cursor="hand2", state=tk.DISABLED, command=self.stop_process ) self.stop_button.pack(side=tk.LEFT, padx=5) self.reload_button = tk.Button( button_frame, text="重新加载工单", font=("微软雅黑", 12), bg="#3498db", fg="white", width=15, height=1, cursor="hand2", command=self.load_work_order ) self.reload_button.pack(side=tk.LEFT, padx=5) # 日志区域 log_frame = tk.LabelFrame(self.root, text="操作日志", font=("微软雅黑", 10), padx=5, pady=5) log_frame.pack(fill=tk.X, padx=10, pady=(0, 10)) self.log_text = tk.Text(log_frame, height=5, font=("Consolas", 9)) self.log_text.pack(fill=tk.BOTH, expand=True) log_scrollbar = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_text.yview) self.log_text.configure(yscrollcommand=log_scrollbar.set) log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) def check_test_mode(self): """检查测试模式""" try: with open("config.json", "r", encoding="utf-8") as f: config = json.load(f) self.test_mode = config.get("test_mode", {}).get("enabled", False) if self.test_mode: self.test_mode_label.config(text="⚠️ 测试模式") self.log("⚠️ 测试模式已启用:失败也算成功", "WARN") else: self.test_mode_label.config(text="") except Exception as e: self.log(f"读取配置失败: {e}", "ERROR") def log(self, message, level="INFO"): """添加日志""" timestamp = datetime.now().strftime("%H:%M:%S") log_message = f"[{timestamp}] [{level}] {message}\n" self.log_text.insert(tk.END, log_message) self.log_text.see(tk.END) print(log_message.strip()) def load_work_order(self): """加载工单数据""" try: with open("work_order.json", "r", encoding="utf-8") as f: self.work_order = json.load(f) # 更新界面 self.order_id_label.config(text=f"工单号: {self.work_order['order_id']}") self.product_label.config(text=f"产品: {self.work_order['product_name']}") self.station_label.config(text=f"工位: {self.work_order['station']}") self.operator_label.config(text=f"操作员: {self.work_order['operator']}") # 更新螺栓列表 self.tree.delete(*self.tree.get_children()) for bolt in self.work_order['bolts']: self.tree.insert("", tk.END, values=( bolt['bolt_id'], bolt['name'], bolt['target_torque'], "待拧紧", "--", "--" ), tags=("pending",)) # 更新进度 total = len(self.work_order['bolts']) self.progress_label.config(text=f"总进度: 0/{total}") self.progress_bar['maximum'] = total self.progress_bar['value'] = 0 self.current_bolt_index = 0 self.log(f"成功加载工单: {self.work_order['order_id']}, 共{total}个螺栓") except Exception as e: messagebox.showerror("错误", f"加载工单失败: {e}") self.log(f"加载工单失败: {e}", "ERROR") def start_process(self): """开始拧紧流程""" if not self.work_order: messagebox.showwarning("警告", "请先加载工单数据") return if self.is_running: return self.is_running = True self.start_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.NORMAL) self.reload_button.config(state=tk.DISABLED) # 启动工作线程 self.thread = threading.Thread(target=self.work_thread, daemon=True) self.thread.start() self.log("开始自动拧紧流程") def stop_process(self): """停止拧紧流程""" self.is_running = False self.start_button.config(state=tk.NORMAL) self.stop_button.config(state=tk.DISABLED) self.reload_button.config(state=tk.NORMAL) self.log("用户停止流程") def work_thread(self): """工作线程""" try: # 连接扳手 self.log("正在连接扳手...") self.wrench = WrenchController() if not self.wrench.connect(): self.log("❌ 连接扳手失败,请检查IP和端口配置", "ERROR") self.root.after(0, lambda: messagebox.showerror( "连接失败", f"无法连接到扳手\nIP: {self.wrench.host}\n端口: {self.wrench.port}\n\n请检查:\n1. 扳手是否开机\n2. 网络连接是否正常\n3. IP和端口配置是否正确" )) self.stop_process() return self.log(f"✅ 已连接到扳手: {self.wrench.host}:{self.wrench.port}", "SUCCESS") # 启用远程控制 self.log("正在启用远程控制...") if not self.wrench.enable_remote_control(True): self.log("❌ 启用远程控制失败", "ERROR") self.root.after(0, lambda: messagebox.showerror("错误", "启用远程控制失败")) self.stop_process() return self.log("✅ 已启用远程控制", "SUCCESS") # 遍历所有螺栓 bolts = self.work_order['bolts'] for index, bolt in enumerate(bolts): if not self.is_running: break self.current_bolt_index = index self.process_bolt(bolt, index) # 完成 if self.is_running: self.log("所有螺栓拧紧完成!", "SUCCESS") self.root.after(0, lambda: messagebox.showinfo("完成", "所有螺栓已成功拧紧!")) except Exception as e: self.log(f"流程异常: {e}", "ERROR") finally: if self.wrench: self.wrench.disconnect() self.root.after(0, self.stop_process) def process_bolt(self, bolt, index): """处理单个螺栓""" bolt_id = bolt['bolt_id'] bolt_name = bolt['name'] target_torque = bolt['target_torque'] self.log(f"开始拧紧: [{bolt_id}] {bolt_name}, 目标扭矩: {target_torque} Nm") # 更新界面 self.root.after(0, lambda: self.update_current_bolt(bolt, "拧紧中...")) self.root.after(0, lambda: self.update_tree_item(index, "拧紧中", "running")) # 设定参数 self.log(f"设定参数: 扭矩={target_torque}Nm, 模式=M{bolt.get('mode', 1)}") self.wrench.set_torque_parameters( target_torque=target_torque, mode=bolt.get('mode', 1), torque_tolerance=bolt.get('torque_tolerance', 0.10), angle_min=bolt.get('angle_min', 1), angle_max=bolt.get('angle_max', 360) ) # 循环尝试,直到成功 attempt = 0 while self.is_running: attempt += 1 self.log(f"第 {attempt} 次尝试拧紧螺栓 {bolt_id}") # 启动扳手 self.log("发送启动命令...") if not self.wrench.start_wrench(direction=1): self.log("❌ 发送启动命令失败", "ERROR") time.sleep(1) continue # 等待结果 self.log("等待扳手响应...") result = self.wrench.wait_for_result() if result and result.get("success"): # 成功 actual_torque = result.get("actual_torque", 0) timestamp = datetime.now().strftime("%H:%M:%S") self.log(f"✅ 螺栓 {bolt_id} 拧紧成功! 实际扭矩: {actual_torque} Nm", "SUCCESS") # 更新界面 self.root.after(0, lambda at=actual_torque, ts=timestamp: self.update_tree_item( index, "成功", "success", at, ts )) self.root.after(0, lambda: self.update_progress(index + 1)) break else: # 失败,继续尝试 if result: self.log(f"❌ 螺栓 {bolt_id} 拧紧失败: {result.get('status', '未知错误')}, 继续重试...", "WARN") else: self.log(f"❌ 螺栓 {bolt_id} 无响应, 继续重试...", "WARN") time.sleep(1) # 等待1秒后重试 # 完成后等待一下 time.sleep(0.5) def update_current_bolt(self, bolt, status): """更新当前螺栓显示""" self.current_bolt_label.config(text=f"[{bolt['bolt_id']}] {bolt['name']}") self.current_torque_label.config(text=f"目标扭矩: {bolt['target_torque']} Nm") self.current_status_label.config(text=f"状态: {status}") def update_tree_item(self, index, status, tag, actual_torque="--", timestamp="--"): """更新表格项""" items = self.tree.get_children() if index < len(items): item = items[index] values = list(self.tree.item(item)['values']) values[3] = status if actual_torque != "--": values[4] = actual_torque if timestamp != "--": values[5] = timestamp self.tree.item(item, values=values, tags=(tag,)) def update_progress(self, completed): """更新进度""" total = len(self.work_order['bolts']) self.progress_label.config(text=f"总进度: {completed}/{total}") self.progress_bar['value'] = completed def main(): root = tk.Tk() app = WrenchGUI(root) root.mainloop() if __name__ == "__main__": main()