上图~
持续迭代
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 = True
self.is_collecting = False
self.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 = False
else:
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 = addresses
logger.info(f"Addresses Config: {', '.join(addresses)}")
# Initialize alarm settings
self.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_val
self.alarm_settings[addr]['upper'] = upper_val
logger.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 &