上图~
持续迭代
1、增加报警弹窗,具体到哪个值,双边规格具体是多少
2、实时显示当前值的统计特征,Max Min AVG ...
import tkinter as tk
from tkinter import simpledialog
import time
import threading
import queue
import logging
from datetime import datetime
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg # 新增导入
from matplotlib.animation import FuncAnimation
from openpyxl import Workbook, load_workbook
from HslCommunication import MelsecMcNet
import os
import csv
import winsound
import json# 配置日志
logging.basicConfig(level=logging.INFO,format='%(asctime)s.%(msecs)03d %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger("PLC_Monitor")# 设置中文字体支持
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号class PLCMonitor:def __init__(self):self.is_running = Trueself.is_collecting = Falseself.data_queue = queue.Queue()self.data_points = []self.register_addresses = ["D390", "D391", "D392", "D393", "D394", "D395"] # 6个默认地址# 新增初始化属性self.current_date = datetime.now().strftime("%Y%m%d") # 当前日期self.current_shift = self.get_shift() # 当前班别self.last_shift_check = time.time() # 上次班别检查时间self.data_dir = "C:\\Log\\Cature_Datawen" # 数据存储目录# 确保数据目录存在os.makedirs(self.data_dir, exist_ok=True)self.current_filepath = os.path.join(self.data_dir, f"{self.current_date}_{self.current_shift}.txt") # 初始化文件路径# 初始化图表属性self.fig, self.ax = plt.subplots(figsize=(12, 6))self.fig.patch.set_facecolor('#2E2E2E') # 设置图表背景色self.canvas = None # 初始化canvas属性# 创建主窗口self.root = tk.Tk()self.root.title("Design By Tim")self.root.geometry("1200x700")self.root.minsize(1000, 600)self.root.configure(bg='#2E2E2E')# 初始化UI组件self.value_labels = [] # 初始化label列表# 创建图表容器self.chart_frame = tk.Frame(self.root, bg='#2E2E2E')self.chart_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=10, pady=10)# 创建值显示区域self.values_frame = tk.Frame(self.root, bg='#2E2E2E')self.values_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=10)# 初始化值标签for i in range(6):label = tk.Label(self.values_frame,text=" ",font=('Arial', 12),bg='#2E2E2E',fg='white')label.pack(side=tk.LEFT, padx=10)self.value_labels.append(label)# 添加analysis按钮 (位置保持不变)self.analysis_button = tk.Button(self.chart_frame,text="analysis",command=self.launch_analysis,font=('Arial', 10),bg='#4E4E4E',fg='white')self.analysis_button.pack(side=tk.TOP, anchor=tk.NE, padx=10, pady=6) # 修改位置到右上角# 添加By Chart按钮 (新位置)self.chart_button = tk.Button(self.chart_frame,text="By Chart",command=self.create_subplots,font=('Arial', 10),bg='#4E4E4E',fg='white')self.chart_button.pack(side=tk.TOP, anchor=tk.NE, padx=10, pady=5)# 添加Alarm Setting按钮 (位置保持不变)self.alarm_button = tk.Button(self.chart_frame,text="Alarm Setting",command=self.open_alarm_settings,font=('Arial', 10),bg='#4E4E4E',fg='white')self.alarm_button.pack(side=tk.TOP, anchor=tk.NE, padx=10, pady=5)# 创建图表self.canvas = FigureCanvasTkAgg(self.fig, master=self.chart_frame)self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)# 隐藏初始的Tk窗口self.root.withdraw()# 获取PLC连接参数self.plc_ip = simpledialog.askstring("PLC Par", "PLC_IP:", initialvalue="192.168.0.11",parent=self.root)self.plc_port = simpledialog.askinteger("PLC Par", "PLC_Port:", initialvalue=1028,parent=self.root)# 获取采集信号配置self.start_signal = simpledialog.askstring("Signal ","Start_signal addresses(Y70):",initialvalue="Y70",parent=self.root)self.stop_signal = simpledialog.askstring("Signal ","Stop_signal addresses(Y75):",initialvalue="Y75",parent=self.root)try:self.plc = MelsecMcNet(self.plc_ip, self.plc_port)if not self.plc.ConnectServer().IsSuccess:logger.error("PLC Content NG")self.is_running = Falseelse:logger.info(f"PLC Content OK: {self.plc_ip}:{self.plc_port}")logger.info(f"Start_signal: {self.start_signal}, Stop_signal: {self.stop_signal}")except Exception as e:logger.error(f"PLC Content Error: {str(e)}")self.is_running = False# 获取寄存器配置register_input = simpledialog.askstring("Addresses Config", "Addresses(Max 6,Betwin ,):",initialvalue="D390,D391,D392,D393,D394,D395")if register_input:addresses = [addr.strip() for addr in register_input.split(',')][:6] # 修改为最多6个地址self.register_addresses = addresseslogger.info(f"Addresses Config: {', '.join(addresses)}")# Initialize alarm settingsself.alarm_settings = {addr: {'upper': float('inf'), 'lower': -float('inf')} for addr in self.register_addresses}self.alarm_active = {addr: False for addr in self.register_addresses}# 加载保存的报警设置self.load_alarm_settings()def open_alarm_settings(self):"""打开报警设置弹窗"""# 创建弹窗alarm_window = tk.Toplevel(self.root)alarm_window.title("Alarm Settings")alarm_window.geometry("400x300")alarm_window.configure(bg='#2E2E2E')alarm_window.transient(self.root)# 创建标签和输入框entries = {}for i, addr in enumerate(self.register_addresses):# 地址标签addr_label = tk.Label(alarm_window,text=f"Address {addr}",font=('Arial', 10),bg='#2E2E2E',fg='white')addr_label.grid(row=i, column=0, padx=10, pady=5, sticky='w')# 下限输入lower_frame = tk.Frame(alarm_window, bg='#2E2E2E')lower_frame.grid(row=i, column=1, padx=5, pady=5)lower_label = tk.Label(lower_frame,text="Lower:",font=('Arial', 8),bg='#2E2E2E',fg='white')lower_label.pack(side=tk.LEFT)lower_entry = tk.Entry(lower_frame, width=10)lower_entry.pack(side=tk.LEFT)lower_entry.insert(0, str(self.alarm_settings[addr]['lower']) if self.alarm_settings[addr]['lower'] != -float('inf') else '')# 上限输入upper_frame = tk.Frame(alarm_window, bg='#2E2E2E')upper_frame.grid(row=i, column=2, padx=5, pady=5)upper_label = tk.Label(upper_frame,text="Upper:",font=('Arial', 8),bg='#2E2E2E',fg='white')upper_label.pack(side=tk.LEFT)upper_entry = tk.Entry(upper_frame, width=10)upper_entry.pack(side=tk.LEFT)upper_entry.insert(0, str(self.alarm_settings[addr]['upper']) if self.alarm_settings[addr]['upper'] != float('inf') else '')entries[addr] = {'lower': lower_entry, 'upper': upper_entry}# 保存按钮def save_settings():for addr, entry in entries.items():try:lower_val = float(entry['lower'].get()) if entry['lower'].get() else -float('inf')upper_val = float(entry['upper'].get()) if entry['upper'].get() else float('inf')self.alarm_settings[addr]['lower'] = lower_valself.alarm_settings[addr]['upper'] = upper_vallogger.info(f"Alarm settings updated for {addr}: Lower={lower_val}, Upper={upper_val}")except ValueError:logger.error(f"Invalid numeric value for {addr}")# 保存报警设置到JSON文件self.save_alarm_settings()alarm_window.destroy()save_btn = tk.Button(alarm_window,text="Save",command=save_settings,font=('Arial', 10),bg='#4E4E4E',fg='white')save_btn.grid(row=len(self.register_addresses), column=1, padx=10, pady=15)# 取消按钮cancel_btn = tk.Button(alarm_window,text="Cancel",command=alarm_window.destroy,font=('Arial', 10),bg='#4E4E4E',fg='white')cancel_btn.grid(row=len(self.register_addresses), column=2, padx=10, pady=15)self.current_date = datetime.now().strftime("%Y%m%d") # 当前日期self.current_shift = self.get_shift() # 当前班别self.last_shift_check = time.time() # 上次班别检查时间def get_shift(self):"""获取当前班别"""now = datetime.now()hour &