上圖~
持續迭代
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 &