557 lines
24 KiB
Python
557 lines
24 KiB
Python
import tkinter as tk
|
||
from tkinter import ttk, messagebox, font
|
||
import serial
|
||
import serial.tools.list_ports
|
||
import threading
|
||
import time
|
||
import struct
|
||
import paho.mqtt.client as mqtt
|
||
from datetime import datetime
|
||
import json
|
||
import logging
|
||
import binascii
|
||
|
||
# 配置日志
|
||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class SensorMQTTApp:
|
||
def __init__(self, root):
|
||
self.root = root
|
||
self.root.title("气体浓度监测系统")
|
||
self.root.geometry("800x600")
|
||
self.root.configure(bg='#f0f0f0')
|
||
|
||
# 变量初始化
|
||
self.serial_port = None
|
||
self.is_collecting = False
|
||
self.is_uploading = False
|
||
self.sensor_responded = False
|
||
self.upload_success = False
|
||
self.data_points = []
|
||
self.sample_interval = 5
|
||
self.current_concentration = 0
|
||
|
||
# MQTT客户端
|
||
self.mqtt_client = None
|
||
self.mqtt_testing = False
|
||
|
||
self.setup_ui()
|
||
self.refresh_serial_ports()
|
||
|
||
def setup_ui(self):
|
||
# 设置主题样式
|
||
style = ttk.Style()
|
||
style.theme_use('clam')
|
||
|
||
# 主框架
|
||
main_frame = ttk.Frame(self.root, padding="8")
|
||
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||
|
||
# 标题
|
||
title_label = tk.Label(main_frame, text="气体浓度实时监测系统",
|
||
font=('Arial', 16, 'bold'), fg='#2c3e50', bg='#f0f0f0')
|
||
title_label.grid(row=0, column=0, columnspan=3, pady=(0, 10))
|
||
|
||
# 配置区域(三列布局)
|
||
config_frame = ttk.Frame(main_frame)
|
||
config_frame.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N), pady=(0, 10))
|
||
|
||
# 第一列:串口设置
|
||
serial_frame = ttk.LabelFrame(config_frame, text="串口设置", padding="8")
|
||
serial_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 5))
|
||
|
||
# 串口号
|
||
ttk.Label(serial_frame, text="串口号:", font=('Arial', 9)).grid(row=0, column=0, sticky=tk.W, pady=3)
|
||
self.port_var = tk.StringVar()
|
||
self.port_combo = ttk.Combobox(serial_frame, textvariable=self.port_var, width=15, state='readonly')
|
||
self.port_combo.grid(row=0, column=1, sticky=tk.W, pady=3, padx=(5, 0))
|
||
|
||
# 波特率
|
||
ttk.Label(serial_frame, text="波特率:", font=('Arial', 9)).grid(row=1, column=0, sticky=tk.W, pady=3)
|
||
self.baudrate_var = tk.StringVar(value="19200")
|
||
baudrate_combo = ttk.Combobox(serial_frame, textvariable=self.baudrate_var,
|
||
values=["9600", "19200", "38400", "57600", "115200"],
|
||
width=15, state='readonly')
|
||
baudrate_combo.grid(row=1, column=1, sticky=tk.W, pady=3, padx=(5, 0))
|
||
|
||
# 设备地址
|
||
ttk.Label(serial_frame, text="设备地址:", font=('Arial', 9)).grid(row=2, column=0, sticky=tk.W, pady=3)
|
||
self.address_var = tk.StringVar(value="1")
|
||
address_spin = ttk.Spinbox(serial_frame, from_=0, to=255, textvariable=self.address_var, width=15)
|
||
address_spin.grid(row=2, column=1, sticky=tk.W, pady=3, padx=(5, 0))
|
||
|
||
# 采样间隔
|
||
ttk.Label(serial_frame, text="采样间隔(秒):", font=('Arial', 9)).grid(row=3, column=0, sticky=tk.W, pady=3)
|
||
self.interval_var = tk.StringVar(value="5")
|
||
interval_spin = ttk.Spinbox(serial_frame, from_=1, to=3600, textvariable=self.interval_var, width=15)
|
||
interval_spin.grid(row=3, column=1, sticky=tk.W, pady=3, padx=(5, 0))
|
||
|
||
# 第二列:云服务设置(加宽文本框)
|
||
mqtt_frame = ttk.LabelFrame(config_frame, text="云服务设置", padding="8")
|
||
mqtt_frame.grid(row=0, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(5, 5))
|
||
|
||
# MQTT服务器地址
|
||
ttk.Label(mqtt_frame, text="服务器地址:", font=('Arial', 9)).grid(row=0, column=0, sticky=tk.W, pady=3)
|
||
self.mqtt_broker_var = tk.StringVar(value="122.112.229.121")
|
||
ttk.Entry(mqtt_frame, textvariable=self.mqtt_broker_var, width=20).grid(row=0, column=1, sticky=tk.W, pady=3, padx=(5, 0))
|
||
|
||
# MQTT端口
|
||
ttk.Label(mqtt_frame, text="端口:", font=('Arial', 9)).grid(row=1, column=0, sticky=tk.W, pady=3)
|
||
self.mqtt_port_var = tk.StringVar(value="1883")
|
||
ttk.Entry(mqtt_frame, textvariable=self.mqtt_port_var, width=20).grid(row=1, column=1, sticky=tk.W, pady=3, padx=(5, 0))
|
||
|
||
# MQTT主题(加宽文本框)
|
||
ttk.Label(mqtt_frame, text="主题:", font=('Arial', 9)).grid(row=2, column=0, sticky=tk.W, pady=3)
|
||
self.mqtt_topic_var = tk.StringVar(value="xiaofang/test")
|
||
ttk.Entry(mqtt_frame, textvariable=self.mqtt_topic_var, width=20).grid(row=2, column=1, sticky=tk.W, pady=3, padx=(5, 0))
|
||
|
||
# MQTT用户名
|
||
ttk.Label(mqtt_frame, text="用户名:", font=('Arial', 9)).grid(row=3, column=0, sticky=tk.W, pady=3)
|
||
self.mqtt_username_var = tk.StringVar(value="xiaofang")
|
||
ttk.Entry(mqtt_frame, textvariable=self.mqtt_username_var, width=20).grid(row=3, column=1, sticky=tk.W, pady=3, padx=(5, 0))
|
||
|
||
# MQTT密码
|
||
ttk.Label(mqtt_frame, text="密码:", font=('Arial', 9)).grid(row=4, column=0, sticky=tk.W, pady=3)
|
||
self.mqtt_password_var = tk.StringVar(value="xiaofang@qwer")
|
||
ttk.Entry(mqtt_frame, textvariable=self.mqtt_password_var, show="*", width=20).grid(row=4, column=1, sticky=tk.W, pady=3, padx=(5, 0))
|
||
|
||
# 第三列:按钮容器和系统状态
|
||
right_column_frame = ttk.Frame(config_frame)
|
||
right_column_frame.grid(row=0, column=2, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(5, 5))
|
||
|
||
# 按钮容器(上下顺序排列)
|
||
button_container = ttk.LabelFrame(right_column_frame, text="操作控制", padding="8")
|
||
button_container.grid(row=0, column=2, sticky=(tk.W, tk.E, tk.N), padx=(5, 0))
|
||
|
||
# 测试MQTT连接按钮
|
||
self.test_mqtt_btn = ttk.Button(button_container, text="测试连接", command=self.start_mqtt_test, width=15)
|
||
self.test_mqtt_btn.pack(pady=5)
|
||
|
||
# 数据采集按钮
|
||
self.collect_btn = ttk.Button(button_container, text="开始采集", command=self.toggle_collection, width=15)
|
||
self.collect_btn.pack(pady=5)
|
||
|
||
# 数据上传按钮
|
||
self.upload_btn = ttk.Button(button_container, text="开始上传", command=self.toggle_upload, width=15)
|
||
self.upload_btn.pack(pady=5)
|
||
|
||
# 系统状态(上下排列)
|
||
status_frame = ttk.LabelFrame(right_column_frame, text="系统状态", padding="8")
|
||
status_frame.grid(row=0, column=3, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(5, 5))
|
||
|
||
# 传感器状态(上下排列)
|
||
sensor_status_frame = ttk.Frame(status_frame)
|
||
sensor_status_frame.pack(fill=tk.X, pady=3)
|
||
ttk.Label(sensor_status_frame, text="传感器:", font=('Arial', 9)).pack(side=tk.LEFT)
|
||
self.sensor_status = tk.Label(sensor_status_frame, text="●", fg="red", font=("Arial", 12, "bold"))
|
||
self.sensor_status.pack(side=tk.LEFT, padx=(5, 5))
|
||
self.sensor_status_text = ttk.Label(sensor_status_frame, text="未连接", font=('Arial', 9))
|
||
self.sensor_status_text.pack(side=tk.LEFT)
|
||
|
||
# 云服务状态(上下排列)
|
||
upload_status_frame = ttk.Frame(status_frame)
|
||
upload_status_frame.pack(fill=tk.X, pady=3)
|
||
ttk.Label(upload_status_frame, text="云服务:", font=('Arial', 9)).pack(side=tk.LEFT)
|
||
self.upload_status = tk.Label(upload_status_frame, text="●", fg="red", font=("Arial", 12, "bold"))
|
||
self.upload_status.pack(side=tk.LEFT, padx=(5, 5))
|
||
self.upload_status_text = ttk.Label(upload_status_frame, text="未连接", font=('Arial', 9))
|
||
self.upload_status_text.pack(side=tk.LEFT)
|
||
|
||
# 网络状态(上下排列)
|
||
network_status_frame = ttk.Frame(status_frame)
|
||
network_status_frame.pack(fill=tk.X, pady=3)
|
||
ttk.Label(network_status_frame, text="网 络:", font=('Arial', 9)).pack(side=tk.LEFT)
|
||
self.mqtt_test_status = tk.Label(network_status_frame, text="●", fg="gray", font=("Arial", 12, "bold"))
|
||
self.mqtt_test_status.pack(side=tk.LEFT, padx=(5, 5))
|
||
self.mqtt_test_status_text = ttk.Label(network_status_frame, text="未测试", font=('Arial', 9))
|
||
self.mqtt_test_status_text.pack(side=tk.LEFT)
|
||
|
||
# 浓度显示区域(跨三列,和数据记录一样宽)
|
||
display_frame = ttk.LabelFrame(main_frame, text="实时浓度监测", padding="12")
|
||
display_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
|
||
|
||
# 浓度值显示(超大字体,宽度与数据记录一致)
|
||
self.concentration_var = tk.StringVar(value="0")
|
||
concentration_label = tk.Label(display_frame, textvariable=self.concentration_var,
|
||
font=('Arial', 36, 'bold'), fg='#2c3e50', bg='white',
|
||
relief='sunken', bd=2, width=25, height=2)
|
||
concentration_label.pack(pady=10)
|
||
|
||
# 单位标签和更新时间
|
||
info_frame = ttk.Frame(display_frame)
|
||
info_frame.pack()
|
||
|
||
unit_label = tk.Label(info_frame, text="ppm", font=('Arial', 14), fg='#7f8c8d')
|
||
unit_label.pack(side=tk.LEFT, padx=(0, 20))
|
||
|
||
self.update_time_var = tk.StringVar(value="未更新")
|
||
time_label = tk.Label(info_frame, textvariable=self.update_time_var,
|
||
font=('Arial', 9), fg='#95a5a6')
|
||
time_label.pack(side=tk.LEFT)
|
||
|
||
# 历史数据区域(跨三列,和浓度显示一样宽)
|
||
history_frame = ttk.LabelFrame(main_frame, text="最近数据记录", padding="6")
|
||
history_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||
|
||
# 创建历史数据文本框(宽度与浓度显示一致)
|
||
self.history_text = tk.Text(history_frame, height=4, width=60, font=('Consolas', 9),
|
||
bg='#f8f9fa', relief='sunken', bd=1)
|
||
scrollbar = ttk.Scrollbar(history_frame, orient=tk.VERTICAL, command=self.history_text.yview)
|
||
self.history_text.configure(yscrollcommand=scrollbar.set)
|
||
|
||
self.history_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||
|
||
# 配置权重
|
||
self.root.columnconfigure(0, weight=1)
|
||
self.root.rowconfigure(0, weight=1)
|
||
|
||
main_frame.columnconfigure(0, weight=1)
|
||
main_frame.columnconfigure(1, weight=1)
|
||
main_frame.columnconfigure(2, weight=1)
|
||
main_frame.rowconfigure(2, weight=1) # 浓度显示区域
|
||
main_frame.rowconfigure(3, weight=1) # 历史数据区域
|
||
|
||
config_frame.columnconfigure(0, weight=1)
|
||
config_frame.columnconfigure(1, weight=2) # 云服务设置更宽
|
||
config_frame.columnconfigure(2, weight=1)
|
||
|
||
serial_frame.columnconfigure(1, weight=1)
|
||
mqtt_frame.columnconfigure(1, weight=1)
|
||
right_column_frame.columnconfigure(0, weight=1)
|
||
|
||
display_frame.columnconfigure(0, weight=1)
|
||
history_frame.columnconfigure(0, weight=1)
|
||
history_frame.rowconfigure(0, weight=1)
|
||
|
||
def refresh_serial_ports(self):
|
||
"""刷新可用串口列表(只在启动时调用一次)"""
|
||
ports = serial.tools.list_ports.comports()
|
||
port_list = [port.device for port in ports]
|
||
self.port_combo['values'] = port_list
|
||
if port_list:
|
||
self.port_combo.current(0)
|
||
|
||
def calculate_crc16(self, data):
|
||
"""计算Modbus CRC16校验"""
|
||
crc = 0xFFFF
|
||
for byte in data:
|
||
crc ^= byte
|
||
for _ in range(8):
|
||
if crc & 0x0001:
|
||
crc = (crc >> 1) ^ 0xA001
|
||
else:
|
||
crc = crc >> 1
|
||
return crc
|
||
|
||
def start_mqtt_test(self):
|
||
"""启动MQTT连接测试"""
|
||
if self.mqtt_testing:
|
||
return
|
||
|
||
self.mqtt_testing = True
|
||
self.test_mqtt_btn.config(state="disabled", text="测试中...")
|
||
self.mqtt_test_status.config(fg="yellow")
|
||
self.mqtt_test_status_text.config(text="测试中")
|
||
|
||
test_thread = threading.Thread(target=self.test_mqtt_connection_thread, daemon=True)
|
||
test_thread.start()
|
||
|
||
def test_mqtt_connection_thread(self):
|
||
"""在单独线程中测试MQTT连接"""
|
||
try:
|
||
client = mqtt.Client()
|
||
connected = False
|
||
|
||
def on_connect(client, userdata, flags, rc):
|
||
nonlocal connected
|
||
connected = (rc == 0)
|
||
|
||
client.on_connect = on_connect
|
||
|
||
username = self.mqtt_username_var.get()
|
||
password = self.mqtt_password_var.get()
|
||
if username and password:
|
||
client.username_pw_set(username, password)
|
||
|
||
client.connect(self.mqtt_broker_var.get(), int(self.mqtt_port_var.get()), 5)
|
||
client.loop_start()
|
||
|
||
timeout = 5
|
||
start_time = time.time()
|
||
while not connected and time.time() - start_time < timeout:
|
||
time.sleep(0.1)
|
||
|
||
client.loop_stop()
|
||
client.disconnect()
|
||
|
||
if connected:
|
||
self.root.after(0, self.on_mqtt_test_success)
|
||
else:
|
||
self.root.after(0, lambda: self.on_mqtt_test_failure("连接失败"))
|
||
|
||
except Exception as e:
|
||
self.root.after(0, lambda: self.on_mqtt_test_failure(str(e)))
|
||
|
||
def on_mqtt_test_success(self):
|
||
"""MQTT测试成功回调"""
|
||
self.mqtt_testing = False
|
||
self.test_mqtt_btn.config(state="normal", text="测试连接")
|
||
self.mqtt_test_status.config(fg="green")
|
||
self.mqtt_test_status_text.config(text="已连接")
|
||
messagebox.showinfo("成功", "MQTT连接测试成功!")
|
||
|
||
def on_mqtt_test_failure(self, error_msg):
|
||
"""MQTT测试失败回调"""
|
||
self.mqtt_testing = False
|
||
self.test_mqtt_btn.config(state="normal", text="测试连接")
|
||
self.mqtt_test_status.config(fg="red")
|
||
self.mqtt_test_status_text.config(text="连接失败")
|
||
messagebox.showerror("错误", f"MQTT连接测试失败: {error_msg}")
|
||
|
||
def build_modbus_command(self, address):
|
||
"""构建Modbus读取命令"""
|
||
command = bytearray([
|
||
address, 0x04, 0x05, 0x20, 0x00, 0x02
|
||
])
|
||
crc = self.calculate_crc16(command)
|
||
command.extend([crc & 0xFF, (crc >> 8) & 0xFF])
|
||
return command
|
||
|
||
def parse_sensor_data(self, response):
|
||
"""解析传感器返回数据"""
|
||
try:
|
||
if len(response) >= 9:
|
||
if response[0] == int(self.address_var.get()) and response[1] == 0x04:
|
||
data_bytes = response[3:7]
|
||
concentration = struct.unpack('>i', data_bytes)[0]
|
||
return concentration
|
||
return None
|
||
except Exception as e:
|
||
logger.error(f"解析传感器数据错误: {e}")
|
||
return None
|
||
|
||
def read_sensor_data(self):
|
||
"""读取传感器数据"""
|
||
try:
|
||
if not self.serial_port or not self.serial_port.is_open:
|
||
return None
|
||
|
||
address = int(self.address_var.get())
|
||
command = self.build_modbus_command(address)
|
||
|
||
self.serial_port.reset_input_buffer()
|
||
self.serial_port.write(command)
|
||
time.sleep(0.1)
|
||
|
||
if self.serial_port.in_waiting > 0:
|
||
response = self.serial_port.read(self.serial_port.in_waiting)
|
||
concentration = self.parse_sensor_data(response)
|
||
|
||
if concentration is not None:
|
||
self.current_concentration = concentration
|
||
self.sensor_responded = True
|
||
self.sensor_status.config(fg="green")
|
||
self.sensor_status_text.config(text="已连接")
|
||
|
||
# 更新显示
|
||
self.update_display(concentration)
|
||
|
||
# 添加到历史数据
|
||
self.add_to_history(concentration)
|
||
|
||
# 如果上传功能开启,上传数据
|
||
if self.is_uploading and self.mqtt_client:
|
||
self.upload_to_mqtt(concentration)
|
||
|
||
return concentration
|
||
else:
|
||
self.sensor_responded = False
|
||
self.sensor_status.config(fg="red")
|
||
self.sensor_status_text.config(text="无响应")
|
||
else:
|
||
self.sensor_responded = False
|
||
self.sensor_status.config(fg="red")
|
||
self.sensor_status_text.config(text="无响应")
|
||
|
||
except Exception as e:
|
||
logger.error(f"读取传感器数据错误: {e}")
|
||
self.sensor_responded = False
|
||
self.sensor_status.config(fg="red")
|
||
self.sensor_status_text.config(text="错误")
|
||
|
||
return None
|
||
|
||
def update_display(self, concentration):
|
||
"""更新显示"""
|
||
self.concentration_var.set(str(concentration))
|
||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
self.update_time_var.set(f"最后更新: {current_time}")
|
||
|
||
def add_to_history(self, concentration):
|
||
"""添加到历史记录"""
|
||
current_time = datetime.now().strftime("%H:%M:%S")
|
||
history_entry = f"{current_time} - {concentration} ppm\n"
|
||
|
||
# 添加到文本框开头
|
||
self.history_text.insert('1.0', history_entry)
|
||
|
||
# 限制历史记录条数
|
||
lines = self.history_text.get('1.0', 'end').count('\n')
|
||
if lines > 10:
|
||
self.history_text.delete('10.0', 'end')
|
||
|
||
def upload_to_mqtt(self, concentration):
|
||
"""上传数据到MQTT"""
|
||
try:
|
||
if self.mqtt_client:
|
||
result = self.mqtt_client.publish(
|
||
self.mqtt_topic_var.get(),
|
||
str(concentration)
|
||
)
|
||
|
||
if result.rc == mqtt.MQTT_ERR_SUCCESS:
|
||
self.upload_success = True
|
||
self.upload_status.config(fg="green")
|
||
self.upload_status_text.config(text="已连接")
|
||
else:
|
||
self.upload_success = False
|
||
self.upload_status.config(fg="red")
|
||
self.upload_status_text.config(text="发送失败")
|
||
|
||
except Exception as e:
|
||
logger.error(f"MQTT上传错误: {e}")
|
||
self.upload_success = False
|
||
self.upload_status.config(fg="red")
|
||
self.upload_status_text.config(text="错误")
|
||
|
||
def connect_mqtt(self):
|
||
"""连接MQTT服务器"""
|
||
try:
|
||
self.mqtt_client = mqtt.Client()
|
||
self.mqtt_client.username_pw_set(
|
||
self.mqtt_username_var.get(),
|
||
self.mqtt_password_var.get()
|
||
)
|
||
|
||
def on_connect(client, userdata, flags, rc):
|
||
if rc == 0:
|
||
logger.info("MQTT连接成功")
|
||
self.upload_status.config(fg="green")
|
||
self.upload_status_text.config(text="已连接")
|
||
else:
|
||
logger.error(f"MQTT连接失败,错误代码: {rc}")
|
||
self.upload_status.config(fg="red")
|
||
self.upload_status_text.config(text="连接失败")
|
||
|
||
self.mqtt_client.on_connect = on_connect
|
||
self.mqtt_client.connect(
|
||
self.mqtt_broker_var.get(),
|
||
int(self.mqtt_port_var.get()),
|
||
60
|
||
)
|
||
self.mqtt_client.loop_start()
|
||
|
||
except Exception as e:
|
||
logger.error(f"MQTT连接错误: {e}")
|
||
messagebox.showerror("错误", f"MQTT连接失败: {str(e)}")
|
||
|
||
def disconnect_mqtt(self):
|
||
"""断开MQTT连接"""
|
||
if self.mqtt_client:
|
||
self.mqtt_client.loop_stop()
|
||
self.mqtt_client.disconnect()
|
||
self.mqtt_client = None
|
||
logger.info("MQTT连接已断开")
|
||
self.upload_status.config(fg="red")
|
||
self.upload_status_text.config(text="未连接")
|
||
|
||
def toggle_collection(self):
|
||
"""切换数据采集状态"""
|
||
if not self.is_collecting:
|
||
try:
|
||
port = self.port_var.get()
|
||
if not port:
|
||
messagebox.showwarning("警告", "请选择串口号")
|
||
return
|
||
|
||
baudrate = int(self.baudrate_var.get())
|
||
self.serial_port = serial.Serial(port, baudrate, timeout=1)
|
||
self.is_collecting = True
|
||
self.collect_btn.config(text="停止采集")
|
||
|
||
# 更新状态显示
|
||
self.sensor_status.config(fg="green")
|
||
self.sensor_status_text.config(text="已连接")
|
||
|
||
# 启动采集线程
|
||
self.collection_thread = threading.Thread(target=self.collection_loop, daemon=True)
|
||
self.collection_thread.start()
|
||
|
||
except Exception as e:
|
||
messagebox.showerror("错误", f"打开串口失败: {str(e)}")
|
||
self.is_collecting = False
|
||
self.collect_btn.config(text="开始采集")
|
||
self.sensor_status.config(fg="red")
|
||
self.sensor_status_text.config(text="错误")
|
||
else:
|
||
self.is_collecting = False
|
||
self.collect_btn.config(text="开始采集")
|
||
if self.serial_port:
|
||
self.serial_port.close()
|
||
self.serial_port = None
|
||
|
||
# 更新状态显示
|
||
self.sensor_status.config(fg="red")
|
||
self.sensor_status_text.config(text="未连接")
|
||
|
||
def toggle_upload(self):
|
||
"""切换数据上传状态"""
|
||
if not self.is_uploading:
|
||
try:
|
||
self.connect_mqtt()
|
||
self.is_uploading = True
|
||
self.upload_btn.config(text="停止上传")
|
||
|
||
except Exception as e:
|
||
messagebox.showerror("错误", f"启动上传失败: {str(e)}")
|
||
self.is_uploading = False
|
||
self.upload_btn.config(text="开始上传")
|
||
else:
|
||
self.is_uploading = False
|
||
self.upload_btn.config(text="开始上传")
|
||
self.disconnect_mqtt()
|
||
|
||
def collection_loop(self):
|
||
"""数据采集循环"""
|
||
while self.is_collecting:
|
||
try:
|
||
self.read_sensor_data()
|
||
interval = int(self.interval_var.get())
|
||
time.sleep(interval)
|
||
except Exception as e:
|
||
logger.error(f"采集循环错误: {e}")
|
||
time.sleep(1)
|
||
|
||
def on_closing(self):
|
||
"""程序关闭时的清理工作"""
|
||
self.is_collecting = False
|
||
self.is_uploading = False
|
||
|
||
if self.serial_port:
|
||
self.serial_port.close()
|
||
|
||
if self.mqtt_client:
|
||
self.disconnect_mqtt()
|
||
|
||
self.root.destroy()
|
||
|
||
def main():
|
||
root = tk.Tk()
|
||
app = SensorMQTTApp(root)
|
||
root.protocol("WM_DELETE_WINDOW", app.on_closing)
|
||
root.mainloop()
|
||
|
||
if __name__ == "__main__":
|
||
main() |