TorqueWrench/wrench_gui.py

448 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/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()