USB设备端口识别监测嵌入式python3自动化测试脚本
软件版本:python3;编译器:IDLE编译器;库:PyAutoGUl库;cmd终端安装PyAutoGUl库命令:pip install pyautogui
一、应用场景简介
嵌入式设备测试开发中,开关机测试;监控特定USB设备的连接状态变化;记录USB端口连接断开次数;其它。——多设备测试,生产线测试,自动化测试
二、功能简介
监测电脑设备管理器的端口的连接(开机)和断开(关机),记录连接和断开的次数。
三、脚本GUI界面功能介绍
使用:配置好VOD,PID后(下方第3点描述VID,PID查看方式),点击开始监测,停止监测。
1.注:使用前查看设备端口是否被正常识别,端口是否可用。Ctrl+x,选择设备管理器,查看CMO端口。
2.日志保存路径:D:\0\test_log,不清空之前的日志,会保留之前的日志。
监控设备配置功能板块:可自行配置VID,PID(下方第3点描述VID,PID查看方式),输入后点击应用配置。
设备状态功能板块:显示端口名,连接时长。
本次监控统计功能板块:记录连接次数,断开次数,监控状态,设备连接断开状态,检查频率为:1s
事件日志功能板块:显示最新的设备连接断开信息。
最底部为监控使用的按钮,开始监控,停止监控,重置统计数量等。
3.查看VID,PID,快捷键ctal+x,设备管理器,端口右键,属性,事件。
代码如下。关注,点赞,欢迎留言评论区。
备注:日志一直在一个文档,不删除历史日志一直累计,有需可改
import tkinter as tk
from tkinter import ttk
import threading
import time
import sys
import os
from datetime import datetime
import json
try:
import serial.tools.list_ports
import serial
SERIAL_AVAILABLE = True
except ImportError:
SERIAL_AVAILABLE = False
print("请先安装pyserial模块: pip install pyserial")
# 目标设备的VID和PID
TARGET_VID = "05C6"
TARGET_PID = "902E"
# 日志保存路径
LOG_DIR = r"D:\0\test_log"
LOG_FILE = os.path.join(LOG_DIR, "usb_monitor_log.txt")
STATS_FILE = os.path.join(LOG_DIR, "usb_statistics.json")
DAILY_LOG_DIR = os.path.join(LOG_DIR, "daily_logs")
class USBMonitorApp:
def __init__(self, root):
self.root = root
self.root.title("USB设备监控器 - 带日志保存")
self.root.geometry("650x500")
# 状态变量
self.device_connected = False
self.monitoring_active = False
self.monitor_thread = None
# 本次监控统计(从开始监控时清零)
self.session_connection_count = 0 # 本次监控连接次数
self.session_disconnection_count = 0 # 本次监控断开次数
self.session_start_time = None # 本次监控开始时间
self.session_running_time = 0 # 本次监控运行时间(秒)
# 设备连接时间记录
self.device_connect_time = None
self.device_disconnect_time = None
# 日志列表
self.log_entries = []
# 创建日志目录
self.setup_log_directory()
self.setup_ui()
# 检查依赖
if not SERIAL_AVAILABLE:
self.show_error("请先安装pyserial模块:\n打开CMD并运行: pip install pyserial")
return
# 记录程序启动
self.log_program_start()
# 默认自动开始监控
self.start_monitoring()
def setup_log_directory(self):
"""创建日志目录"""
try:
os.makedirs(LOG_DIR, exist_ok=True)
os.makedirs(DAILY_LOG_DIR, exist_ok=True)
print(f"日志目录已创建: {LOG_DIR}")
except Exception as e:
print(f"创建日志目录失败: {e}")
def setup_ui(self):
# 创建主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 标题和路径显示
title_frame = ttk.Frame(main_frame)
title_frame.grid(row=0, column=0, columnspan=3, pady=(0, 10), sticky=(tk.W, tk.E))
title_label = ttk.Label(title_frame, text="USB设备监控系统", font=('Arial', 16, 'bold'))
title_label.grid(row=0, column=0, sticky=tk.W)
# 显示日志路径
log_path_label = ttk.Label(title_frame, text=f"日志路径: {LOG_DIR}", font=('Arial', 8), foreground="gray")
log_path_label.grid(row=1, column=0, sticky=tk.W, pady=(2, 0))
# 设备信息
info_frame = ttk.LabelFrame(main_frame, text="监控设备信息", padding="10")
info_frame.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
ttk.Label(info_frame, text="VID:").grid(row=0, column=0, sticky=tk.W)
ttk.Label(info_frame, text=TARGET_VID, font=('Arial', 10, 'bold'), foreground="blue").grid(row=0, column=1, sticky=tk.W, padx=(5, 20))
ttk.Label(info_frame, text="PID:").grid(row=0, column=2, sticky=tk.W)
ttk.Label(info_frame, text=TARGET_PID, font=('Arial', 10, 'bold'), foreground="blue").grid(row=0, column=3, sticky=tk.W, padx=(5, 0))
# 状态显示
status_frame = ttk.LabelFrame(main_frame, text="设备状态", padding="10")
status_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
self.status_label = ttk.Label(status_frame, text="正在检测...", font=('Arial', 12))
self.status_label.grid(row=0, column=0, sticky=tk.W, padx=(10, 20))
self.status_indicator = tk.Canvas(status_frame, width=20, height=20, bg='gray')
self.status_indicator.grid(row=0, column=1, sticky=tk.W)
# 当前连接时长显示
self.connection_duration_label = ttk.Label(status_frame, text="连接时长: --:--:--", font=('Arial', 9))
self.connection_duration_label.grid(row=0, column=2, sticky=tk.W, padx=(30, 0))
# 连接统计(本次监控)
count_frame = ttk.LabelFrame(main_frame, text="本次监控统计", padding="10")
count_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
# 连接次数
ttk.Label(count_frame, text="连接次数:").grid(row=0, column=0, sticky=tk.W)
self.connection_label = ttk.Label(count_frame, text="0", font=('Arial', 12, 'bold'), foreground="green")
self.connection_label.grid(row=0, column=1, sticky=tk.W, padx=(5, 30))
# 断开次数
ttk.Label(count_frame, text="断开次数:").grid(row=0, column=2, sticky=tk.W)
self.disconnection_label = ttk.Label(count_frame, text="0", font=('Arial', 12, 'bold'), foreground="red")
self.disconnection_label.grid(row=0, column=3, sticky=tk.W, padx=(5, 0))
# 运行时间
ttk.Label(count_frame, text="运行时间:").grid(row=0, column=4, sticky=tk.W, padx=(20, 5))
self.runtime_label = ttk.Label(count_frame, text="00:00:00", font=('Arial', 10, 'bold'))
self.runtime_label.grid(row=0, column=5, sticky=tk.W)
# 监控状态
ttk.Label(count_frame, text="监控状态:").grid(row=1, column=0, sticky=tk.W, pady=(5, 0))
self.monitoring_status_label = ttk.Label(count_frame, text="未开始", font=('Arial', 10, 'bold'))
self.monitoring_status_label.grid(row=1, column=1, sticky=tk.W, padx=(5, 30), pady=(5, 0))
# 当前状态
ttk.Label(count_frame, text="设备状态:").grid(row=1, column=2, sticky=tk.W, pady=(5, 0))
self.device_status_label = ttk.Label(count_frame, text="未知", font=('Arial', 10, 'bold'))
self.device_status_label.grid(row=1, column=3, sticky=tk.W, padx=(5, 0), pady=(5, 0))
# 检查频率
ttk.Label(count_frame, text="检查频率:").grid(row=1, column=4, sticky=tk.W, padx=(20, 5), pady=(5, 0))
self.check_freq_label = ttk.Label(count_frame, text="1秒", font=('Arial', 10, 'bold'))
self.check_freq_label.grid(row=1, column=5, sticky=tk.W, pady=(5, 0))
# 日志区域
log_frame = ttk.LabelFrame(main_frame, text="事件日志", padding="10")
log_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
# 创建日志框架
self.log_frame_inner = ttk.Frame(log_frame)
self.log_frame_inner.pack(fill=tk.BOTH, expand=True)
# 创建滚动条
scrollbar = ttk.Scrollbar(self.log_frame_inner)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 创建日志文本框
self.log_text = tk.Text(
self.log_frame_inner,
height=10,
width=70,
yscrollcommand=scrollbar.set,
bg='#f5f5f5',
relief=tk.FLAT,
font=('Consolas', 9)
)
self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=self.log_text.yview)
# 控制按钮
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=5, column=0, columnspan=3, pady=(10, 0))
self.start_button = ttk.Button(button_frame, text="▶ 开始监控", command=self.start_monitoring)
self.start_button.grid(row=0, column=0, padx=(0, 10))
self.stop_button = ttk.Button(button_frame, text="■ 停止监控", command=self.stop_monitoring, state='disabled')
self.stop_button.grid(row=0, column=1, padx=(0, 10))
self.reset_button = ttk.Button(button_frame, text="🔄 重置统计", command=self.reset_statistics)
self.reset_button.grid(row=0, column=2, padx=(0, 10))
self.clear_button = ttk.Button(button_frame, text="🗑️ 清空日志", command=self.clear_log)
self.clear_button.grid(row=0, column=3, padx=(0, 10))
self.copy_button = ttk.Button(button_frame, text="📋 复制日志", command=self.copy_log)
self.copy_button.grid(row=0, column=4, padx=(0, 10))
self.save_button = ttk.Button(button_frame, text="💾 保存日志", command=self.save_log)
self.save_button.grid(row=0, column=5, padx=(0, 10))
self.view_button = ttk.Button(button_frame, text="📂 打开日志", command=self.open_log_folder)
self.view_button.grid(row=0, column=6, padx=(0, 10))
self.quit_button = ttk.Button(button_frame, text="✕ 退出", command=self.quit_app)
self.quit_button.grid(row=0, column=7)
# 配置网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(4, weight=1)
# 初始化运行时间更新
self.update_runtime()
self.update_connection_duration()
def update_runtime(self):
"""更新运行时间"""
if self.monitoring_active and self.session_start_time:
elapsed = int(time.time() - self.session_start_time)
self.session_running_time = elapsed
hours = elapsed // 3600
minutes = (elapsed % 3600) // 60
seconds = elapsed % 60
self.runtime_label.config(text=f"{hours:02d}:{minutes:02d}:{seconds:02d}")
# 每秒更新一次
self.root.after(1000, self.update_runtime)
def update_connection_duration(self):
"""更新连接时长"""
if self.device_connected and self.device_connect_time:
elapsed = int(time.time() - self.device_connect_time)
hours = elapsed // 3600
minutes = (elapsed % 3600) // 60
seconds = elapsed % 60
self.connection_duration_label.config(text=f"连接时长: {hours:02d}:{minutes:02d}:{seconds:02d}")
else:
self.connection_duration_label.config(text="连接时长: --:--:--")
# 每秒更新一次
self.root.after(1000, self.update_connection_duration)
def check_device(self):
"""检查目标设备是否连接"""
try:
ports = serial.tools.list_ports.comports()
for port in ports:
if hasattr(port, 'vid') and port.vid:
vid_hex = f"{port.vid:04X}".upper()
pid_hex = f"{port.pid:04X}".upper() if port.pid else "0000"
if vid_hex == TARGET_VID and pid_hex == TARGET_PID:
return True, port.device, port.description
return False, None, None
except Exception as e:
self.log_event(f"检查设备时出错: {str(e)}", "error")
return False, None, None
def update_status(self, connected, port_info=None, description=None):
"""更新UI状态"""
current_status = "connected" if connected else "disconnected"
if connected and not self.device_connected:
# 设备刚刚连接
self.session_connection_count += 1
self.device_connected = True
self.device_connect_time = time.time()
# 更新UI
self.status_label.config(text=f"已连接 - {port_info}")
self.status_indicator.config(bg='green')
self.connection_label.config(text=str(self.session_connection_count))
self.device_status_label.config(text="已连接", foreground="green")
# 保存到文件
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{timestamp}] 设备连接 - 端口: {port_info}, 描述: {description}"
self.log_event(f"[{timestamp}] ✓ 设备已连接 - 端口: {port_info}, 描述: {description}", "connect")
self.save_to_log_file(log_entry, "CONNECT")
# 播放连接提示音
self.root.bell()
elif not connected and self.device_connected:
# 设备刚刚断开
self.session_disconnection_count += 1
self.device_connected = False
# 计算连接时长
duration = ""
if self.device_connect_time:
connect_duration = int(time.time() - self.device_connect_time)
hours = connect_duration // 3600
minutes = (connect_duration % 3600) // 60
seconds = connect_duration % 60
duration = f" (连接时长: {hours:02d}:{minutes:02d}:{seconds:02d})"
self.device_connect_time = None
# 更新UI
self.status_label.config(text="未连接")
self.status_indicator.config(bg='red')
self.disconnection_label.config(text=str(self.session_disconnection_count))
self.device_status_label.config(text="未连接", foreground="red")
# 保存到文件
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{timestamp}] 设备断开{duration}"
self.log_event(f"[{timestamp}] ✗ 设备已断开{duration}", "disconnect")
self.save_to_log_file(log_entry, "DISCONNECT")
# 播放断开提示音
self.root.bell()
elif connected and self.device_connected:
# 设备保持连接状态
self.status_label.config(text=f"已连接 - {port_info}")
self.status_indicator.config(bg='green')
self.device_status_label.config(text="已连接", foreground="green")
else:
# 设备保持断开状态
self.status_label.config(text="未连接")
self.status_indicator.config(bg='red')
self.device_status_label.config(text="未连接", foreground="red")
# 保存统计数据
self.save_statistics()
def reset_statistics(self):
"""重置统计信息"""
if self.monitoring_active:
response = tk.messagebox.askyesno("确认", "监控正在进行中,重置统计将清空当前数据。\n是否继续?")
if not response:
return
# 重置统计
self.session_connection_count = 0
self.session_disconnection_count = 0
self.session_running_time = 0
# 更新UI
self.connection_label.config(text="0")
self.disconnection_label.config(text="0")
self.runtime_label.config(text="00:00:00")
self.status_label.config(text="未连接")
self.status_indicator.config(bg='gray')
self.device_status_label.config(text="未知")
self.connection_duration_label.config(text="连接时长: --:--:--")
# 清空日志
self.clear_log()
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.log_event(f"[{timestamp}] 🔄 统计信息已重置", "info")
# 如果是监控中,重新开始计时
if self.monitoring_active:
self.session_start_time = time.time()
self.log_event(f"[{timestamp}] ⏱️ 监控计时已重置", "info")
def save_to_log_file(self, message, event_type):
"""保存日志到文件"""
try:
# 保存到主日志文件
with open(LOG_FILE, 'a', encoding='utf-8') as f:
f.write(f"{message}\n")
# 保存到每日日志文件
today = datetime.now().strftime("%Y%m%d")
daily_log_file = os.path.join(DAILY_LOG_DIR, f"usb_log_{today}.txt")
with open(daily_log_file, 'a', encoding='utf-8') as f:
f.write(f"{message}\n")
except Exception as e:
self.log_event(f"保存日志到文件失败: {str(e)}", "error")
def save_to_detailed_log(self, data):
"""保存详细日志(JSON格式)"""
try:
detailed_log = {
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"event": data.get("event"),
"port": data.get("port"),
"description": data.get("description"),
"duration": data.get("duration"),
"session_connections": self.session_connection_count,
"session_disconnections": self.session_disconnection_count,
"session_runtime": self.session_running_time
}
today = datetime.now().strftime("%Y%m%d")
detailed_log_file = os.path.join(DAILY_LOG_DIR, f"detailed_log_{today}.json")
# 读取现有数据或创建新列表
if os.path.exists(detailed_log_file):
with open(detailed_log_file, 'r', encoding='utf-8') as f:
logs = json.load(f)
else:
logs = []
# 添加新日志
logs.append(detailed_log)
# 保存回文件
with open(detailed_log_file, 'w', encoding='utf-8') as f:
json.dump(logs, f, ensure_ascii=False, indent=2)
except Exception as e:
self.log_event(f"保存详细日志失败: {str(e)}", "error")
def log_event(self, message, event_type="info"):
"""添加日志条目到UI"""
timestamp = datetime.now().strftime("%H:%M:%S")
# 为不同事件类型设置不同颜色
if event_type == "connect":
tag_color = "green"
elif event_type == "disconnect":
tag_color = "red"
elif event_type == "error":
tag_color = "orange"
else:
tag_color = "black"
# 插入带时间戳的日志
self.log_text.insert(tk.END, message + "\n")
# 为最近的消息设置颜色标签
start_index = self.log_text.search(message.split("\n")[0], "end-1c linestart", stopindex="1.0")
if start_index:
end_index = f"{start_index}+{len(message)}c"
self.log_text.tag_add(event_type, start_index, end_index)
self.log_text.tag_config(event_type, foreground=tag_color)
self.log_text.see(tk.END) # 自动滚动到底部
# 保存到日志列表
self.log_entries.append(message)
def load_statistics(self):
"""加载历史统计数据(现在只用于读取,不影响本次监控统计)"""
try:
if os.path.exists(STATS_FILE):
with open(STATS_FILE, 'r', encoding='utf-8') as f:
stats = json.load(f)
# 只读取,不用于显示
total_connections = stats.get("total_connections", 0)
total_disconnections = stats.get("total_disconnections", 0)
# 记录到日志但不显示在UI
timestamp = datetime.now().strftime("%H:%M:%S")
self.log_event(f"[{timestamp}] 📊 历史统计: 总连接 {total_connections} 次, 总断开 {total_disconnections} 次", "info")
except Exception as e:
self.log_event(f"读取历史统计失败: {str(e)}", "error")
def save_statistics(self):
"""保存统计数据"""
try:
# 读取现有统计数据
if os.path.exists(STATS_FILE):
with open(STATS_FILE, 'r', encoding='utf-8') as f:
stats = json.load(f)
else:
stats = {}
# 更新统计数据
total_connections = stats.get("total_connections", 0) + self.session_connection_count
total_disconnections = stats.get("total_disconnections", 0) + self.session_disconnection_count
stats.update({
"total_connections": total_connections,
"total_disconnections": total_disconnections,
"last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"last_session_connections": self.session_connection_count,
"last_session_disconnections": self.session_disconnection_count,
"last_session_runtime": self.session_running_time
})
with open(STATS_FILE, 'w', encoding='utf-8') as f:
json.dump(stats, f, ensure_ascii=False, indent=2)
except Exception as e:
self.log_event(f"保存统计数据失败: {str(e)}", "error")
def log_program_start(self):
"""记录程序启动"""
try:
start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{start_time}] ========== 程序启动 =========="
# 保存到日志文件
with open(LOG_FILE, 'a', encoding='utf-8') as f:
f.write(f"\n{log_entry}\n")
# 在UI中显示
self.log_event(f"[{datetime.now().strftime('%H:%M:%S')}] ✅ 程序启动 - 日志保存到: {LOG_DIR}", "info")
# 加载历史统计(仅显示)
self.load_statistics()
except Exception as e:
self.log_event(f"记录程序启动失败: {str(e)}", "error")
def clear_log(self):
"""清空日志"""
self.log_text.delete(1.0, tk.END)
self.log_entries.clear()
self.log_event(f"[{datetime.now().strftime('%H:%M:%S')}] 📝 日志已清空", "info")
def copy_log(self):
"""复制日志到剪贴板"""
try:
log_content = self.log_text.get(1.0, tk.END).strip()
if log_content:
self.root.clipboard_clear()
self.root.clipboard_append(log_content)
self.log_event(f"[{datetime.now().strftime('%H:%M:%S')}] 日志已复制到剪贴板", "info")
except Exception as e:
self.log_event(f"复制日志时出错: {str(e)}", "error")
def save_log(self):
"""手动保存日志"""
try:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
manual_log_file = os.path.join(LOG_DIR, f"manual_save_{timestamp}.txt")
with open(manual_log_file, 'w', encoding='utf-8') as f:
f.write(self.log_text.get(1.0, tk.END))
self.log_event(f"[{datetime.now().strftime('%H:%M:%S')}] 日志已手动保存到: {manual_log_file}", "info")
except Exception as e:
self.log_event(f"保存日志失败: {str(e)}", "error")
def open_log_folder(self):
"""打开日志文件夹"""
try:
os.startfile(LOG_DIR)
self.log_event(f"[{datetime.now().strftime('%H:%M:%S')}] 已打开日志文件夹", "info")
except Exception as e:
self.log_event(f"打开日志文件夹失败: {str(e)}", "error")
def monitor_loop(self):
"""监控循环"""
check_interval = 1 # 检查间隔(秒)
while self.monitoring_active:
try:
connected, port, description = self.check_device()
# 在UI线程中更新状态
self.root.after(0, lambda c=connected, p=port, d=description: self.update_status(c, p, d))
# 等待下次检查
for _ in range(check_interval * 10):
if not self.monitoring_active:
break
time.sleep(0.1)
except Exception as e:
self.root.after(0, lambda: self.log_event(f"监控循环错误: {str(e)}", "error"))
time.sleep(check_interval)
def start_monitoring(self):
"""开始监控"""
if self.monitoring_active:
return
self.monitoring_active = True
self.session_start_time = time.time()
self.start_button.config(state='disabled')
self.stop_button.config(state='normal')
self.monitoring_status_label.config(text="运行中", foreground="green")
# 记录监控开始时间
self.current_session_start = datetime.now()
self.save_to_log_file(f"[{self.current_session_start.strftime('%Y-%m-%d %H:%M:%S')}] 监控开始", "INFO")
# 初始检查
connected, port, description = self.check_device()
self.update_status(connected, port, description)
# 启动监控线程
self.monitor_thread = threading.Thread(target=self.monitor_loop, daemon=True)
self.monitor_thread.start()
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.log_event(f"[{timestamp}] ✅ 开始监控设备 VID:{TARGET_VID}, PID:{TARGET_PID}", "info")
self.log_event(f"[{timestamp}] 📊 统计从此刻开始: 连接次数={self.session_connection_count}, 断开次数={self.session_disconnection_count}, 运行时间=00:00:00", "info")
def stop_monitoring(self):
"""停止监控"""
if not self.monitoring_active:
return
self.monitoring_active = False
self.start_button.config(state='normal')
self.stop_button.config(state='disabled')
self.monitoring_status_label.config(text="已停止", foreground="red")
# 记录监控结束时间
if self.session_start_time:
duration = int(time.time() - self.session_start_time)
hours = duration // 3600
minutes = (duration % 3600) // 60
seconds = duration % 60
duration_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
self.save_to_log_file(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 监控结束 (本次运行时长: {duration_str}, 连接: {self.session_connection_count}, 断开: {self.session_disconnection_count})", "INFO")
if self.monitor_thread:
self.monitor_thread.join(timeout=2)
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.log_event(f"[{timestamp}] ⏹️ 停止监控", "info")
self.log_event(f"[{timestamp}] 📊 本次监控统计: 连接次数={self.session_connection_count}, 断开次数={self.session_disconnection_count}, 运行时间={self.runtime_label.cget('text')}", "info")
# 保存统计数据
self.save_statistics()
def show_error(self, message):
"""显示错误信息(在日志区域)"""
error_msg = f"[{datetime.now().strftime('%H:%M:%S')}] ❌ 错误: {message}"
self.log_event(error_msg, "error")
def quit_app(self):
"""退出应用程序"""
# 记录程序关闭
try:
end_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{end_time}] ========== 程序关闭 ==========\n"
with open(LOG_FILE, 'a', encoding='utf-8') as f:
f.write(f"{log_entry}\n")
except:
pass
# 保存统计数据
self.save_statistics()
# 停止监控
self.stop_monitoring()
# 关闭窗口
self.root.quit()
self.root.destroy()
def on_closing(self):
"""窗口关闭事件处理"""
self.quit_app()
def main():
if not SERIAL_AVAILABLE:
print("请先安装pyserial模块: pip install pyserial")
input("按Enter键退出...")
return
root = tk.Tk()
# 设置窗口样式
root.style = ttk.Style()
root.style.theme_use('clam')
app = USBMonitorApp(root)
# 设置窗口关闭事件
root.protocol("WM_DELETE_WINDOW", app.on_closing)
# 设置窗口图标(可选)
try:
root.iconbitmap(default='icon.ico')
except:
pass
root.mainloop()
if __name__ == "__main__":
main()
