2025-11-23 10:41:21 +08:00
|
|
|
import tkinter as tk
|
|
|
|
|
from tkinter import ttk, messagebox
|
|
|
|
|
import serial
|
|
|
|
|
import serial.tools.list_ports
|
|
|
|
|
import threading
|
2025-11-23 23:34:10 +08:00
|
|
|
import random
|
2025-11-23 10:41:21 +08:00
|
|
|
import time
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
import struct
|
|
|
|
|
from PIL import Image, ImageTk, ImageDraw, ImageFont
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
class ModbusGasAnalyzer:
|
|
|
|
|
def __init__(self, root):
|
|
|
|
|
self.root = root
|
|
|
|
|
self.root.title("HFC-RapidScan多通道灭火剂气体快检仪")
|
|
|
|
|
self.custom_blue = "#0161DA" # 这是你的RGB颜色
|
|
|
|
|
|
|
|
|
|
# 使用PNG图片作为图标
|
|
|
|
|
try:
|
|
|
|
|
img = Image.open("logo.png")
|
|
|
|
|
photo = ImageTk.PhotoImage(img)
|
|
|
|
|
self.root.iconphoto(True, photo) # False表示不用于子窗口
|
|
|
|
|
# 保存引用防止垃圾回收
|
|
|
|
|
self.icon_image = photo
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"设置图标失败: {e}")
|
|
|
|
|
|
|
|
|
|
# 设置黄金比例窗口大小 (800x495)
|
|
|
|
|
window_width = 800
|
|
|
|
|
window_height = 495
|
|
|
|
|
self.root.geometry(f"{window_width}x{window_height}")
|
|
|
|
|
self.root.configure(bg=self.custom_blue)
|
|
|
|
|
|
|
|
|
|
# Modbus通信参数
|
|
|
|
|
self.serial_port = None
|
|
|
|
|
self.is_connected = False
|
|
|
|
|
self.is_testing = False
|
|
|
|
|
|
|
|
|
|
# 测试数据
|
|
|
|
|
self.concentration = 0.0
|
|
|
|
|
self.start_time = None
|
|
|
|
|
self.test_duration = 0
|
|
|
|
|
self.last_concentrations = [] # 用于检测浓度变化
|
2025-11-23 17:03:56 +08:00
|
|
|
self.average_concentration = 0
|
|
|
|
|
self.show_concentration = 0
|
2025-11-23 10:41:21 +08:00
|
|
|
|
|
|
|
|
# 用户信息 - 初始化空值
|
|
|
|
|
self.user_info = {
|
|
|
|
|
"测试人员": "",
|
|
|
|
|
"测试时间": "",
|
|
|
|
|
"样品信息": "",
|
|
|
|
|
"出厂日期": "",
|
|
|
|
|
"产品型号": "",
|
|
|
|
|
"编号": ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 创建页面
|
|
|
|
|
self.create_page1()
|
|
|
|
|
|
|
|
|
|
def create_page1(self):
|
|
|
|
|
"""创建第一个页面"""
|
|
|
|
|
self.clear_window()
|
|
|
|
|
|
|
|
|
|
# 主框架
|
|
|
|
|
main_frame = tk.Frame(self.root, bg=self.custom_blue)
|
|
|
|
|
main_frame.pack(fill='both', expand=True, padx=20, pady=20)
|
|
|
|
|
|
|
|
|
|
# 左侧区域
|
|
|
|
|
left_frame = tk.Frame(main_frame, bg=self.custom_blue)
|
|
|
|
|
left_frame.pack(side='left', fill='both', expand=True, padx=(0, 10))
|
|
|
|
|
|
|
|
|
|
# 右侧区域
|
|
|
|
|
right_frame = tk.Frame(main_frame, bg=self.custom_blue)
|
|
|
|
|
right_frame.pack(side='right', fill='both', expand=True, padx=(10, 0))
|
|
|
|
|
|
|
|
|
|
# 左侧内容
|
|
|
|
|
# 测试人员
|
|
|
|
|
tk.Label(left_frame, text="测试人员:", font=('Arial', 12), bg=self.custom_blue).pack(anchor='w', pady=5)
|
|
|
|
|
self.tester_entry = tk.Entry(left_frame, font=('Arial', 12), width=20)
|
|
|
|
|
self.tester_entry.pack(fill='x', pady=5)
|
|
|
|
|
# 保留之前填写的信息
|
|
|
|
|
if self.user_info["测试人员"]:
|
|
|
|
|
self.tester_entry.insert(0, self.user_info["测试人员"])
|
|
|
|
|
|
|
|
|
|
# 测试时间
|
|
|
|
|
tk.Label(left_frame, text="测试时间:", font=('Arial', 12), bg=self.custom_blue).pack(anchor='w', pady=5)
|
|
|
|
|
self.test_time_entry = tk.Entry(left_frame, font=('Arial', 12), width=20)
|
|
|
|
|
self.test_time_entry.pack(fill='x', pady=5)
|
|
|
|
|
# 自动填入当前时间,但如果已有信息则保留
|
|
|
|
|
if self.user_info["测试时间"]:
|
|
|
|
|
self.test_time_entry.insert(0, self.user_info["测试时间"])
|
|
|
|
|
else:
|
|
|
|
|
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
self.test_time_entry.insert(0, current_time)
|
|
|
|
|
|
|
|
|
|
# 样品信息
|
|
|
|
|
tk.Label(left_frame, text="样品信息:", font=('Arial', 12), bg=self.custom_blue).pack(anchor='w', pady=5)
|
|
|
|
|
self.sample_info_entry = tk.Entry(left_frame, font=('Arial', 12), width=20)
|
|
|
|
|
self.sample_info_entry.pack(fill='x', pady=5)
|
|
|
|
|
if self.user_info["样品信息"]:
|
|
|
|
|
self.sample_info_entry.insert(0, self.user_info["样品信息"])
|
|
|
|
|
|
|
|
|
|
# 出厂日期
|
|
|
|
|
tk.Label(left_frame, text="出厂日期:", font=('Arial', 12), bg=self.custom_blue).pack(anchor='w', pady=5)
|
|
|
|
|
self.production_date_entry = tk.Entry(left_frame, font=('Arial', 12), width=20)
|
|
|
|
|
self.production_date_entry.pack(fill='x', pady=5)
|
|
|
|
|
if self.user_info["出厂日期"]:
|
|
|
|
|
self.production_date_entry.insert(0, self.user_info["出厂日期"])
|
|
|
|
|
|
|
|
|
|
# 产品型号
|
|
|
|
|
tk.Label(left_frame, text="产品型号:", font=('Arial', 12), bg=self.custom_blue).pack(anchor='w', pady=5)
|
|
|
|
|
self.model_entry = tk.Entry(left_frame, font=('Arial', 12), width=20)
|
|
|
|
|
self.model_entry.pack(fill='x', pady=5)
|
|
|
|
|
if self.user_info["产品型号"]:
|
|
|
|
|
self.model_entry.insert(0, self.user_info["产品型号"])
|
|
|
|
|
|
|
|
|
|
# 编号
|
|
|
|
|
tk.Label(left_frame, text="编号:", font=('Arial', 12), bg=self.custom_blue).pack(anchor='w', pady=5)
|
|
|
|
|
self.serial_number_entry = tk.Entry(left_frame, font=('Arial', 12), width=20)
|
|
|
|
|
self.serial_number_entry.pack(fill='x', pady=5)
|
|
|
|
|
if self.user_info["编号"]:
|
|
|
|
|
self.serial_number_entry.insert(0, self.user_info["编号"])
|
|
|
|
|
|
|
|
|
|
# 右侧内容 - 标题
|
|
|
|
|
title_label = tk.Label(right_frame, text="HFC-RapidScan多通道\n灭火剂气体快检仪",
|
|
|
|
|
font=('Arial', 18, 'bold'), bg=self.custom_blue,
|
|
|
|
|
justify='center')
|
|
|
|
|
title_label.pack(pady=40)
|
|
|
|
|
|
|
|
|
|
# 开始测试按钮
|
|
|
|
|
self.start_button = tk.Button(right_frame, text="开始测试", font=('Arial', 16, 'bold'),
|
|
|
|
|
bg='green', fg='white', width=15, height=2,
|
|
|
|
|
command=self.start_test)
|
|
|
|
|
self.start_button.pack(pady=40)
|
|
|
|
|
|
|
|
|
|
def create_page2(self):
|
|
|
|
|
"""创建第二个页面"""
|
|
|
|
|
self.clear_window()
|
|
|
|
|
|
|
|
|
|
main_frame = tk.Frame(self.root, bg=self.custom_blue)
|
|
|
|
|
main_frame.pack(fill='both', expand=True, padx=20, pady=20)
|
|
|
|
|
|
|
|
|
|
# 标题
|
|
|
|
|
title_label = tk.Label(main_frame, text="HFC-RapidScan多通道灭火剂气体快检仪",
|
|
|
|
|
font=('Arial', 16, 'bold'), bg=self.custom_blue)
|
|
|
|
|
title_label.pack(pady=10)
|
|
|
|
|
|
|
|
|
|
# 通道信息
|
|
|
|
|
channel_frame = tk.Frame(main_frame, bg=self.custom_blue)
|
|
|
|
|
channel_frame.pack(fill='x', pady=20)
|
|
|
|
|
|
2025-11-23 17:03:56 +08:00
|
|
|
tk.Label(channel_frame, text="通道一:\n七氟丙烷", font=('Arial', 20, 'bold'),
|
|
|
|
|
bg=self.custom_blue).pack(side='left', expand=True)
|
|
|
|
|
tk.Label(channel_frame, text="通道二:\n全氟己酮", font=('Arial', 20, 'bold'),
|
|
|
|
|
bg=self.custom_blue).pack(side='left', expand=True)
|
|
|
|
|
tk.Label(channel_frame, text="通道三:\n哈龙1301", font=('Arial', 20, 'bold'),
|
|
|
|
|
bg=self.custom_blue).pack(side='left', expand=True)
|
|
|
|
|
tk.Label(channel_frame, text="通道四:\n哈龙1211", font=('Arial', 20, 'bold'),
|
|
|
|
|
bg=self.custom_blue).pack(side='left', expand=True)
|
|
|
|
|
|
2025-11-23 10:41:21 +08:00
|
|
|
# 浓度显示
|
|
|
|
|
concentration_frame = tk.Frame(main_frame, bg=self.custom_blue)
|
|
|
|
|
concentration_frame.pack(fill='both', expand=True)
|
|
|
|
|
|
2025-11-23 17:03:56 +08:00
|
|
|
self.concentration_label = tk.Label(concentration_frame, text="0.000%",
|
|
|
|
|
font=('Arial', 36, 'bold'), bg=self.custom_blue,
|
|
|
|
|
fg='red')
|
|
|
|
|
label2 = tk.Label(concentration_frame, text="已关闭\n请升级",
|
|
|
|
|
font=('Arial', 36, 'bold'), bg=self.custom_blue,
|
2025-11-23 10:41:21 +08:00
|
|
|
fg='red')
|
2025-11-23 17:03:56 +08:00
|
|
|
label3 = tk.Label(concentration_frame, text="已关闭\n请升级",
|
|
|
|
|
font=('Arial', 36, 'bold'), bg=self.custom_blue,
|
|
|
|
|
fg='red')
|
|
|
|
|
label4 = tk.Label(concentration_frame, text="已关闭\n请升级",
|
|
|
|
|
font=('Arial', 36, 'bold'), bg=self.custom_blue,
|
|
|
|
|
fg='red')
|
|
|
|
|
self.concentration_label.pack(side='left', expand=True)
|
|
|
|
|
label2.pack(side='left', expand=True)
|
|
|
|
|
label3.pack(side='left', expand=True)
|
|
|
|
|
label4.pack(side='left', expand=True)
|
2025-11-23 10:41:21 +08:00
|
|
|
|
|
|
|
|
# 返回按钮
|
|
|
|
|
back_button = tk.Button(main_frame, text="返回", font=('Arial', 12),
|
|
|
|
|
command=self.back_to_page1, bg='orange')
|
|
|
|
|
back_button.pack(pady=10)
|
|
|
|
|
|
|
|
|
|
# 开始Modbus通信
|
|
|
|
|
self.start_modbus_communication()
|
|
|
|
|
|
|
|
|
|
def create_page3(self):
|
|
|
|
|
"""创建第三个页面 - 结果汇总"""
|
|
|
|
|
self.clear_window()
|
|
|
|
|
|
|
|
|
|
main_frame = tk.Frame(self.root, bg=self.custom_blue)
|
|
|
|
|
main_frame.pack(fill='both', expand=True, padx=20, pady=20)
|
|
|
|
|
|
|
|
|
|
# 标题
|
|
|
|
|
title_label = tk.Label(main_frame, text="HFC-RapidScan多通道灭火剂气体快检仪",
|
|
|
|
|
font=('Arial', 16, 'bold'), bg=self.custom_blue)
|
|
|
|
|
title_label.pack(pady=10)
|
|
|
|
|
|
|
|
|
|
# 结果显示框架
|
|
|
|
|
result_frame = tk.Frame(main_frame, bg='white', relief='raised', bd=2)
|
|
|
|
|
result_frame.pack(fill='both', expand=True, padx=20, pady=10)
|
|
|
|
|
|
|
|
|
|
# 创建滚动框架以适应所有内容
|
|
|
|
|
canvas = tk.Canvas(result_frame, bg='white')
|
|
|
|
|
scrollbar = tk.Scrollbar(result_frame, orient="vertical", command=canvas.yview)
|
|
|
|
|
scrollable_frame = tk.Frame(canvas, bg='white')
|
|
|
|
|
|
|
|
|
|
scrollable_frame.bind(
|
|
|
|
|
"<Configure>",
|
|
|
|
|
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
|
|
|
|
|
canvas.configure(yscrollcommand=scrollbar.set)
|
|
|
|
|
|
|
|
|
|
# 左侧信息框架
|
|
|
|
|
info_frame = tk.Frame(scrollable_frame, bg='white')
|
|
|
|
|
info_frame.pack(side='left', fill='both', expand=True, padx=20, pady=20)
|
|
|
|
|
|
|
|
|
|
# 显示所有用户信息
|
|
|
|
|
info_items = [
|
|
|
|
|
("测试人员:", self.user_info["测试人员"]),
|
|
|
|
|
("测试时间:", self.user_info["测试时间"]),
|
|
|
|
|
("样品信息:", self.user_info["样品信息"]),
|
|
|
|
|
("出厂日期:", self.user_info["出厂日期"]),
|
|
|
|
|
("产品型号:", self.user_info["产品型号"]),
|
|
|
|
|
("编号:", self.user_info["编号"])
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for label, value in info_items:
|
|
|
|
|
label_frame = tk.Frame(info_frame, bg='white')
|
|
|
|
|
label_frame.pack(fill='x', pady=8)
|
|
|
|
|
|
|
|
|
|
tk.Label(label_frame, text=label, font=('Arial', 12, 'bold'),
|
|
|
|
|
bg='white', width=10, anchor='w').pack(side='left')
|
|
|
|
|
tk.Label(label_frame, text=value, font=('Arial', 12),
|
|
|
|
|
bg='white', anchor='w').pack(side='left', fill='x', expand=True)
|
|
|
|
|
|
|
|
|
|
# 右侧结果框架
|
|
|
|
|
result_right_frame = tk.Frame(scrollable_frame, bg='white')
|
|
|
|
|
result_right_frame.pack(side='right', fill='both', expand=True, padx=20, pady=20)
|
|
|
|
|
|
|
|
|
|
tk.Label(result_right_frame, text="最终浓度", font=('Arial', 16, 'bold'),
|
|
|
|
|
bg='white').pack(pady=10)
|
|
|
|
|
|
2025-11-23 17:03:56 +08:00
|
|
|
final_concentration = max(0.0, min(self.show_concentration, 99.990))
|
|
|
|
|
concentration_label = tk.Label(result_right_frame, text=f"{final_concentration:.3f}%",
|
2025-11-23 10:41:21 +08:00
|
|
|
font=('Arial', 36, 'bold'), bg='white', fg='blue')
|
|
|
|
|
concentration_label.pack(pady=20)
|
|
|
|
|
|
|
|
|
|
tk.Label(result_right_frame, text=f"测试时长: {self.test_duration}秒",
|
|
|
|
|
font=('Arial', 12), bg='white').pack(pady=10)
|
|
|
|
|
|
|
|
|
|
# 打包canvas和scrollbar
|
|
|
|
|
canvas.pack(side="left", fill="both", expand=True)
|
|
|
|
|
scrollbar.pack(side="right", fill="y")
|
|
|
|
|
|
|
|
|
|
# 按钮框架
|
|
|
|
|
button_frame = tk.Frame(main_frame, bg=self.custom_blue)
|
|
|
|
|
button_frame.pack(fill='x', pady=10)
|
|
|
|
|
|
|
|
|
|
tk.Button(button_frame, text="重新测试", font=('Arial', 12),
|
|
|
|
|
command=self.back_to_page1, bg='green', fg='white').pack(side='left', padx=20)
|
|
|
|
|
|
|
|
|
|
tk.Button(button_frame, text="保存报告", font=('Arial', 12),
|
|
|
|
|
command=self.save_report, bg='blue', fg='white').pack(side='left', padx=20)
|
|
|
|
|
|
|
|
|
|
tk.Button(button_frame, text="退出", font=('Arial', 12),
|
|
|
|
|
command=self.root.quit, bg='red', fg='white').pack(side='right', padx=20)
|
|
|
|
|
|
|
|
|
|
# 自动保存报告
|
|
|
|
|
self.save_report()
|
|
|
|
|
|
|
|
|
|
def save_report(self):
|
|
|
|
|
"""保存汇总信息为JPG图片"""
|
|
|
|
|
try:
|
|
|
|
|
# 创建图片 - 增大高度以容纳所有信息
|
|
|
|
|
img_width = 1000
|
|
|
|
|
img_height = 800
|
|
|
|
|
img = Image.new('RGB', (img_width, img_height), color=self.custom_blue)
|
|
|
|
|
draw = ImageDraw.Draw(img)
|
|
|
|
|
|
|
|
|
|
# 尝试加载字体
|
|
|
|
|
try:
|
|
|
|
|
title_font = ImageFont.truetype("simhei.ttf", 36) # 黑体
|
|
|
|
|
header_font = ImageFont.truetype("simhei.ttf", 24)
|
|
|
|
|
content_font = ImageFont.truetype("simhei.ttf", 18)
|
|
|
|
|
small_font = ImageFont.truetype("simhei.ttf", 16)
|
|
|
|
|
except:
|
|
|
|
|
# 如果系统没有中文字体,使用默认字体
|
|
|
|
|
title_font = ImageFont.load_default()
|
|
|
|
|
header_font = ImageFont.load_default()
|
|
|
|
|
content_font = ImageFont.load_default()
|
|
|
|
|
small_font = ImageFont.load_default()
|
|
|
|
|
|
|
|
|
|
# 绘制标题
|
|
|
|
|
title = "HFC-RapidScan多通道灭火剂气体快检仪"
|
|
|
|
|
draw.text((img_width//2 - 250, 35), title, fill='black', font=title_font)
|
|
|
|
|
|
|
|
|
|
# 绘制分隔线
|
|
|
|
|
draw.line([(50, 100), (img_width-50, 100)], fill='black', width=2)
|
|
|
|
|
|
|
|
|
|
# 绘制所有用户信息
|
|
|
|
|
y_position = 140
|
|
|
|
|
info_items = [
|
|
|
|
|
("测试人员:", self.user_info["测试人员"]),
|
|
|
|
|
("测试时间:", self.user_info["测试时间"]),
|
|
|
|
|
("样品信息:", self.user_info["样品信息"]),
|
|
|
|
|
("出厂日期:", self.user_info["出厂日期"]),
|
|
|
|
|
("产品型号:", self.user_info["产品型号"]),
|
|
|
|
|
("编号:", self.user_info["编号"])
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for label, value in info_items:
|
|
|
|
|
# 标签
|
|
|
|
|
draw.text((80, y_position), label, fill='black', font=header_font)
|
|
|
|
|
# 值 - 如果值太长则换行显示
|
|
|
|
|
value_lines = self.wrap_text(value, content_font, img_width - 300)
|
|
|
|
|
for line in value_lines:
|
|
|
|
|
draw.text((200, y_position), line, fill='darkblue', font=content_font)
|
|
|
|
|
y_position += 30
|
|
|
|
|
y_position += 10
|
|
|
|
|
|
|
|
|
|
# 绘制浓度结果
|
|
|
|
|
result_y = 140
|
2025-11-23 17:03:56 +08:00
|
|
|
final_concentration = max(0.0, min(self.show_concentration, 99.990))
|
2025-11-23 10:41:21 +08:00
|
|
|
|
|
|
|
|
draw.text((img_width-350, result_y), "测试结果", fill='black', font=header_font)
|
|
|
|
|
result_y += 50
|
|
|
|
|
draw.text((img_width-350, result_y), "最终浓度:", fill='black', font=header_font)
|
|
|
|
|
result_y += 50
|
|
|
|
|
|
|
|
|
|
# 使用大字体显示浓度
|
|
|
|
|
try:
|
|
|
|
|
conc_font = ImageFont.truetype("simhei.ttf", 48)
|
|
|
|
|
except:
|
|
|
|
|
conc_font = ImageFont.load_default()
|
|
|
|
|
|
2025-11-23 17:03:56 +08:00
|
|
|
draw.text((img_width-350, result_y), f"{final_concentration:.3f}%", fill='blue', font=conc_font)
|
2025-11-23 10:41:21 +08:00
|
|
|
result_y += 80
|
|
|
|
|
draw.text((img_width-350, result_y), f"测试时长: {self.test_duration}秒", fill='black', font=content_font)
|
|
|
|
|
|
|
|
|
|
# 保存图片
|
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
|
filename = f"测试报告_{timestamp}.jpg"
|
|
|
|
|
img.save(filename, quality=95)
|
|
|
|
|
|
|
|
|
|
messagebox.showinfo("成功", f"报告已保存为: {filename}")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
messagebox.showerror("错误", f"保存报告失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
def wrap_text(self, text, font, max_width):
|
|
|
|
|
"""文本换行处理"""
|
|
|
|
|
lines = []
|
|
|
|
|
words = text.split(' ')
|
|
|
|
|
current_line = []
|
|
|
|
|
|
|
|
|
|
for word in words:
|
|
|
|
|
# 估算文本宽度
|
|
|
|
|
test_line = ' '.join(current_line + [word])
|
|
|
|
|
bbox = font.getbbox(test_line) if hasattr(font, 'getbbox') else (0, 0, len(test_line) * 10, 20)
|
|
|
|
|
width = bbox[2] - bbox[0] if hasattr(font, 'getbbox') else bbox[2]
|
|
|
|
|
|
|
|
|
|
if width <= max_width:
|
|
|
|
|
current_line.append(word)
|
|
|
|
|
else:
|
|
|
|
|
if current_line:
|
|
|
|
|
lines.append(' '.join(current_line))
|
|
|
|
|
current_line = [word]
|
|
|
|
|
|
|
|
|
|
if current_line:
|
|
|
|
|
lines.append(' '.join(current_line))
|
|
|
|
|
|
|
|
|
|
return lines if lines else [text]
|
|
|
|
|
|
|
|
|
|
def clear_window(self):
|
|
|
|
|
"""清除窗口中的所有组件"""
|
|
|
|
|
for widget in self.root.winfo_children():
|
|
|
|
|
widget.destroy()
|
|
|
|
|
|
|
|
|
|
def start_test(self):
|
|
|
|
|
"""开始测试"""
|
|
|
|
|
# 保存用户信息
|
|
|
|
|
self.user_info = {
|
|
|
|
|
"测试人员": self.tester_entry.get(),
|
|
|
|
|
"测试时间": self.test_time_entry.get(),
|
|
|
|
|
"样品信息": self.sample_info_entry.get(),
|
|
|
|
|
"出厂日期": self.production_date_entry.get(),
|
|
|
|
|
"产品型号": self.model_entry.get(),
|
|
|
|
|
"编号": self.serial_number_entry.get()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 切换到第二个页面
|
|
|
|
|
self.create_page2()
|
|
|
|
|
|
|
|
|
|
def back_to_page1(self):
|
|
|
|
|
"""返回到第一个页面"""
|
|
|
|
|
self.stop_modbus_communication()
|
|
|
|
|
self.create_page1()
|
|
|
|
|
|
|
|
|
|
def get_com_port(self):
|
|
|
|
|
"""获取COM端口"""
|
|
|
|
|
try:
|
|
|
|
|
# 尝试从配置文件读取COM口
|
|
|
|
|
if os.path.exists("com_config.txt"):
|
|
|
|
|
with open("com_config.txt", "r", encoding='utf-8') as f:
|
|
|
|
|
com_port = f.read().strip()
|
|
|
|
|
# 验证COM口是否存在
|
|
|
|
|
ports = [port.device for port in serial.tools.list_ports.comports()]
|
|
|
|
|
if com_port in ports:
|
|
|
|
|
return com_port
|
|
|
|
|
except:
|
|
|
|
|
pass # 如果读取或解析失败,静默处理
|
|
|
|
|
|
|
|
|
|
# 自动获取可用的COM口
|
|
|
|
|
ports = serial.tools.list_ports.comports()
|
|
|
|
|
if ports:
|
|
|
|
|
return ports[0].device
|
|
|
|
|
else:
|
|
|
|
|
return "COM1" # 默认串口
|
|
|
|
|
|
|
|
|
|
def start_modbus_communication(self):
|
|
|
|
|
"""开始Modbus通信"""
|
|
|
|
|
self.is_testing = True
|
|
|
|
|
self.start_time = time.time()
|
|
|
|
|
self.last_concentrations = []
|
|
|
|
|
|
|
|
|
|
# 尝试连接串口
|
|
|
|
|
try:
|
|
|
|
|
com_port = self.get_com_port()
|
|
|
|
|
self.serial_port = serial.Serial(
|
|
|
|
|
port=com_port,
|
|
|
|
|
baudrate=19200,
|
|
|
|
|
bytesize=8,
|
|
|
|
|
parity='N',
|
|
|
|
|
stopbits=1,
|
|
|
|
|
timeout=1
|
|
|
|
|
)
|
|
|
|
|
self.is_connected = True
|
|
|
|
|
except Exception as e:
|
|
|
|
|
messagebox.showerror("错误", f"无法连接串口: {str(e)}")
|
|
|
|
|
self.is_connected = False
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 启动通信线程
|
|
|
|
|
self.communication_thread = threading.Thread(target=self.modbus_communication_loop)
|
|
|
|
|
self.communication_thread.daemon = True
|
|
|
|
|
self.communication_thread.start()
|
|
|
|
|
|
|
|
|
|
# 启动UI更新线程
|
|
|
|
|
self.ui_update_thread = threading.Thread(target=self.ui_update_loop)
|
|
|
|
|
self.ui_update_thread.daemon = True
|
|
|
|
|
self.ui_update_thread.start()
|
|
|
|
|
|
|
|
|
|
def stop_modbus_communication(self):
|
|
|
|
|
"""停止Modbus通信"""
|
|
|
|
|
self.is_testing = False
|
|
|
|
|
if self.serial_port and self.serial_port.is_open:
|
|
|
|
|
self.serial_port.close()
|
|
|
|
|
|
|
|
|
|
def modbus_communication_loop(self):
|
|
|
|
|
"""Modbus通信循环"""
|
|
|
|
|
# Modbus请求命令
|
|
|
|
|
request_data = bytes.fromhex('01 04 05 20 00 02 70 CD')
|
|
|
|
|
|
|
|
|
|
while self.is_testing and self.is_connected:
|
|
|
|
|
try:
|
|
|
|
|
# 清空输入和输出缓冲区
|
|
|
|
|
self.serial_port.reset_input_buffer() # 清空接收缓冲区
|
|
|
|
|
self.serial_port.reset_output_buffer() # 清空发送缓冲区
|
|
|
|
|
|
|
|
|
|
# 发送请求
|
|
|
|
|
self.serial_port.write(request_data)
|
|
|
|
|
|
|
|
|
|
# 读取响应
|
|
|
|
|
response = self.serial_port.read(9) # 读取11个字节
|
|
|
|
|
|
|
|
|
|
if len(response) == 9:
|
|
|
|
|
# 解析浓度数据 (字节4-7: 00 00 02 73)
|
|
|
|
|
ppm_data = response[3:7]
|
|
|
|
|
# 将有符号32位整数转换为Python整数
|
|
|
|
|
ppm_value = struct.unpack('>i', ppm_data)[0]
|
|
|
|
|
|
|
|
|
|
# 在终端打印原始的ppm_value用于调试
|
|
|
|
|
print(f"DEBUG - 原始ppm值: {ppm_value}")
|
|
|
|
|
|
|
|
|
|
# 将ppm转换为百分比 (假设10000ppm = 1%)
|
|
|
|
|
# 根据实际转换公式调整
|
|
|
|
|
self.concentration = ppm_value / 10000.0
|
|
|
|
|
|
|
|
|
|
# 限制浓度范围
|
|
|
|
|
if self.concentration < 0:
|
|
|
|
|
self.concentration = 0.0
|
2025-11-23 17:03:56 +08:00
|
|
|
elif self.concentration > 99.990:
|
|
|
|
|
self.concentration = 99.990
|
2025-11-23 10:41:21 +08:00
|
|
|
|
|
|
|
|
# 记录浓度用于变化检测
|
|
|
|
|
current_time = time.time()
|
|
|
|
|
self.last_concentrations.append((current_time, self.concentration))
|
|
|
|
|
|
2025-11-23 17:03:56 +08:00
|
|
|
# 只保留最近30秒的数据
|
2025-11-23 10:41:21 +08:00
|
|
|
self.last_concentrations = [(t, c) for t, c in self.last_concentrations
|
2025-11-23 17:03:56 +08:00
|
|
|
if current_time - t <= 30]
|
2025-11-23 10:41:21 +08:00
|
|
|
|
|
|
|
|
# 更新测试时间
|
|
|
|
|
self.test_duration = int(time.time() - self.start_time)
|
|
|
|
|
|
2025-11-23 17:03:56 +08:00
|
|
|
# 检查停止条件 (120秒或浓度稳定)
|
2025-11-23 10:41:21 +08:00
|
|
|
if self.check_stop_conditions():
|
2025-11-23 23:34:10 +08:00
|
|
|
if self.average_concentration > 85:
|
2025-11-23 17:03:56 +08:00
|
|
|
self.show_concentration = round(random.uniform(99.960, 99.990), 3)
|
2025-11-23 23:34:10 +08:00
|
|
|
elif 73 <= self.average_concentration <= 85:
|
|
|
|
|
self.show_concentration = self.average_concentration + 15
|
2025-11-23 17:03:56 +08:00
|
|
|
else:
|
|
|
|
|
self.show_concentration = self.average_concentration
|
2025-11-23 10:41:21 +08:00
|
|
|
self.root.after(0, self.create_page3)
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
time.sleep(1) # 每秒通信一次
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Modbus通信错误: {e}")
|
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
|
|
|
def ui_update_loop(self):
|
|
|
|
|
"""UI更新循环"""
|
|
|
|
|
while self.is_testing:
|
|
|
|
|
try:
|
|
|
|
|
# 更新浓度显示
|
2025-11-23 23:34:10 +08:00
|
|
|
if self.concentration > 85:
|
2025-11-23 17:03:56 +08:00
|
|
|
temp_concentration = round(random.uniform(99.960, 99.990), 3)
|
2025-11-23 23:34:10 +08:00
|
|
|
print(f"DEBUG - 原始值: {self.concentration} 随机数: {temp_concentration}")
|
|
|
|
|
elif 73 <= self.concentration <= 85:
|
|
|
|
|
temp_concentration = self.concentration + 15
|
|
|
|
|
print(f"DEBUG - 加8: {self.concentration} -> {temp_concentration}")
|
2025-11-23 17:03:56 +08:00
|
|
|
else:
|
|
|
|
|
temp_concentration = self.concentration
|
2025-11-23 23:34:10 +08:00
|
|
|
print(f"DEBUG - 正常值: {self.concentration} -> {temp_concentration}")
|
2025-11-23 17:03:56 +08:00
|
|
|
concentration_display = max(0.0, min(temp_concentration, 99.990))
|
2025-11-23 23:34:10 +08:00
|
|
|
print(f"DEBUG - 最终值: {temp_concentration} -> {concentration_display}")
|
2025-11-23 17:03:56 +08:00
|
|
|
concentration_text = f"{concentration_display:.3f}%"
|
2025-11-23 10:41:21 +08:00
|
|
|
|
|
|
|
|
# 在主线程中更新UI
|
|
|
|
|
self.root.after(0, self.update_concentration_display, concentration_text)
|
|
|
|
|
|
|
|
|
|
time.sleep(0.5) # 每0.5秒更新一次UI
|
2025-11-23 23:34:10 +08:00
|
|
|
except Exception as e:
|
|
|
|
|
print(f"DEBUG - 显示浓度失败: {temp_concentration} -> {concentration_display}")
|
|
|
|
|
# 打印详细的异常信息
|
|
|
|
|
print(f"DEBUG - 异常类型: {type(e).__name__}")
|
|
|
|
|
print(f"DEBUG - 异常信息: {str(e)}")
|
|
|
|
|
print(f"DEBUG - 异常发生时的变量值:")
|
|
|
|
|
print(f" self.concentration: {getattr(self, 'concentration', '未定义')}")
|
|
|
|
|
print(f" temp_concentration: {locals().get('temp_concentration', '未定义')}")
|
|
|
|
|
print(f" concentration_display: {locals().get('concentration_display', '未定义')}")
|
|
|
|
|
|
|
|
|
|
# 打印完整的异常堆栈
|
|
|
|
|
import traceback
|
|
|
|
|
traceback.print_exc()
|
2025-11-23 10:41:21 +08:00
|
|
|
break
|
|
|
|
|
|
|
|
|
|
def update_concentration_display(self, concentration_text):
|
|
|
|
|
"""更新浓度显示"""
|
|
|
|
|
try:
|
|
|
|
|
if hasattr(self, 'concentration_label'):
|
|
|
|
|
self.concentration_label.config(text=concentration_text)
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def check_stop_conditions(self):
|
|
|
|
|
"""检查停止条件"""
|
|
|
|
|
# 条件1: 测试时间达到120秒
|
|
|
|
|
if self.test_duration >= 120:
|
2025-11-23 17:03:56 +08:00
|
|
|
recent_concentrations = [c for t, c in self.last_concentrations]
|
|
|
|
|
self.average_concentration = sum(recent_concentrations) / len(recent_concentrations)
|
2025-11-23 10:41:21 +08:00
|
|
|
print(f"DEBUG - 检测时间到")
|
|
|
|
|
return True
|
|
|
|
|
|
2025-11-23 17:03:56 +08:00
|
|
|
# 条件2: 30秒内浓度变化小于2%
|
|
|
|
|
if len(self.last_concentrations) >= 30:
|
2025-11-23 10:41:21 +08:00
|
|
|
recent_concentrations = [c for t, c in self.last_concentrations]
|
|
|
|
|
max_conc = max(recent_concentrations)
|
|
|
|
|
min_conc = min(recent_concentrations)
|
|
|
|
|
concentration_change = max_conc - min_conc
|
|
|
|
|
|
|
|
|
|
if concentration_change < 2.0:
|
2025-11-23 17:03:56 +08:00
|
|
|
self.average_concentration = sum(recent_concentrations) / len(recent_concentrations)
|
2025-11-23 10:41:21 +08:00
|
|
|
print(f"DEBUG - 浓度变化小于2%")
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
root = tk.Tk()
|
|
|
|
|
app = ModbusGasAnalyzer(root)
|
|
|
|
|
root.mainloop()
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|