448 lines
17 KiB
Python
448 lines
17 KiB
Python
|
|
#!/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()
|