You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tkinter单设备温湿度数据独立展示与自动更新问题求解

ESP32温湿度监控Tkinter GUI修复方案

现存问题

  • 点击不同设备的按钮时,所有按钮显示相同数据
  • 设备数据无法按预期每2秒自动刷新

问题分析

  1. 全局变量覆盖:用self.result_button存储设备按钮,新添加的设备会覆盖这个变量,导致所有操作都指向最后一个设备。
  2. 设备信息未绑定:获取数据的函数直接从添加窗口输入框读取内容,没有将ID、IP与对应设备按钮关联,无法区分不同设备。
  3. 刷新逻辑无针对性:自动刷新函数没有绑定具体设备信息,无法单独更新每个设备的数据。
  4. 多窗口错误:添加设备和详情窗口使用tk.Tk()创建新主窗口,违反Tkinter单主窗口原则,可能引发异常。

修复后的完整代码

import tkinter as tk
from tkinter import messagebox, Toplevel
from PIL import ImageTk, Image
import requests

class MainApplication(tk.Tk):
    def __init__(self):
        super().__init__()
        self.button_counter = 0  # 替换全局变量为实例变量
        self.button_position = [
            (100, 100), (300, 100), (500, 100), (700, 100), (900, 100), (1100, 100), (1300, 100),
            (100, 200), (300, 200), (500, 200), (700, 200), (900, 200), (1100, 200), (1300, 200),
            (100, 300), (300, 300), (500, 300), (700, 300), (900, 300), (1100, 300), (1300, 300),
            (100, 400), (300, 400), (500, 400), (700, 400), (900, 400), (1100, 400), (1300, 400),
            (100, 500), (300, 500), (500, 500), (700, 500), (900, 500), (1100, 500), (1300, 500),
            (100, 600), (300, 600), (500, 600), (700, 600), (900, 600), (1100, 600), (1300, 600)
        ]
        self.devices = []  # 存储所有设备信息:{id, ip, button, refresh_id, last_data}

        self.frame = tk.Frame(self, height=50, width=500)
        self.frame.pack()

        # 加载logo(注意路径转义或用原始字符串)
        logo_image = Image.open(r'C:\Users\Kuralmozhi.R\Downloads\icons8-temperature-65.png')
        test = ImageTk.PhotoImage(logo_image)
        self.logo = tk.Label(self.frame, image=test)
        self.logo.image = test
        self.logo.pack(side=tk.LEFT)

        self.title = tk.Label(self.frame,
                              text="ESP32 Temperature & Humidity Measurement",
                              fg="DodgerBlue2",
                              font='Times 25 bold')
        self.title.pack(side=tk.LEFT)

        self.status_frame = tk.Frame(self, bd=1, bg="DodgerBlue2", relief=tk.SUNKEN)
        self.status_frame.pack(side=tk.BOTTOM, fill=tk.X)

        self.button1 = tk.Button(self.status_frame,
                                 text="+",
                                 bg="DodgerBlue2",
                                 fg="white",
                                 font='Helvetica 20 bold',
                                 relief=tk.FLAT,
                                 command=self.add_device)
        self.button1.pack(side=tk.RIGHT, padx=5)

    def add_device(self):
        # 使用Toplevel创建子窗口,而非新主窗口
        self.window = Toplevel(self)
        self.window.title("Add new device")
        self.window.geometry("700x400")
        self.window.resizable(0, 0)

        self.id_label = tk.Label(self.window, text="Enter ID Number: ", font='Helvetica 12 bold')
        self.id_label.place(x=200, y=100)

        self.id_entry = tk.Entry(self.window)
        self.id_entry.place(x=350, y=100)

        self.ip_label = tk.Label(self.window, text="Enter IP Address: ", font='Helvetica 12 bold')
        self.ip_label.place(x=200, y=150)

        self.ip_entry = tk.Entry(self.window)
        self.ip_entry.place(x=350, y=150)

        self.add_button = tk.Button(self.window,
                                    text="Add",
                                    bg="DodgerBlue2",
                                    fg="white",
                                    font='Helvetica 12 bold',
                                    command=self.add_device_confirm)
        self.add_button.place(x=350, y=200)

    def add_device_confirm(self):
        device_id = self.id_entry.get().strip()
        device_ip = self.ip_entry.get().strip()

        if not device_id or not device_ip:
            messagebox.showerror("Error", "ID和IP不能为空")
            return

        # 创建设备按钮
        if self.button_counter >= len(self.button_position):
            messagebox.showwarning("Warning", "设备数量已达上限")
            self.window.destroy()
            return

        x, y = self.button_position[self.button_counter]
        self.button_counter += 1

        result_button = tk.Button(self,
                                  text=f"Device {device_id}\n加载中...",
                                  font="Times 15",
                                  bg="SkyBlue1",
                                  fg="black",
                                  highlightthickness=2)
        result_button.place(x=x, y=y)

        # 存储设备信息,绑定按钮与ID、IP
        device_info = {
            'id': device_id,
            'ip': device_ip,
            'button': result_button,
            'refresh_id': None,
            'last_data': f"Device {device_id}\n加载中..."
        }
        self.devices.append(device_info)

        # 设置按钮点击事件,绑定当前设备信息
        result_button.config(command=lambda d=device_info: [self.get_ip_data(d), self.show_details(d)])

        # 首次获取数据并启动自动刷新
        self.get_ip_data(device_info)
        self.start_auto_refresh(device_info)

        self.window.destroy()
        messagebox.showinfo("Success", "设备添加成功")

    def get_ip_data(self, device_info):
        device_id = device_info['id']
        device_ip = device_info['ip']
        button = device_info['button']

        try:
            response = requests.get(f"http://{device_ip}/", timeout=5)
            response.raise_for_status()
            data = response.json()

            temp = round(data['temperature'], 2)
            humi = round(data['humidity'], 2)
            id_json = str(data['id'])

            if id_json == device_id:
                button_text = f"Device {device_id}\nTemperature: {temp}°C\nHumidity: {humi} %"
                button.config(text=button_text)
                device_info['last_data'] = button_text  # 存储最新数据用于详情展示
            else:
                messagebox.showerror("Error!", f"设备{device_id}返回ID不匹配")
        except Exception as e:
            error_text = f"Device {device_id}\n连接失败"
            button.config(text=error_text)
            device_info['last_data'] = error_text
            messagebox.showerror("Error!", f"获取设备{device_id}数据失败: {str(e)}")

    def start_auto_refresh(self, device_info):
        # 先取消之前的刷新任务(如果有)
        if device_info['refresh_id']:
            self.after_cancel(device_info['refresh_id'])
        # 定时刷新当前设备数据
        refresh_id = self.after(2000, lambda: [self.get_ip_data(device_info), self.start_auto_refresh(device_info)])
        device_info['refresh_id'] = refresh_id

    def show_details(self, device_info):
        # 使用Toplevel创建详情窗口
        new_window = Toplevel(self)
        new_window.geometry("500x500")
        new_window.config(bg="LightSkyBlue1")
        new_window.resizable(0, 0)
        new_window.title(f"设备{device_info['id']}详情")

        display_text = device_info.get('last_data', f"Device {device_info['id']}\n暂无数据")
        self.label = tk.Label(new_window,
                              text=display_text,
                              bg="LightSkyBlue1",
                              fg="black",
                              font="Times 20 bold")
        self.label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)

if __name__ == "__main__":
    app = MainApplication()
    app.geometry("1400x700")
    app.mainloop()

关键修改说明

  • 设备信息管理:新增self.devices列表存储每个设备的ID、IP、按钮组件、刷新任务ID和最新数据,避免全局变量覆盖问题。
  • 绑定设备数据:创建按钮时将设备信息通过lambda绑定到点击事件,确保每个按钮操作对应自身设备。
  • 修复多窗口问题:所有子窗口改用Toplevel创建,遵循Tkinter单主窗口设计原则。
  • 针对性自动刷新:每个设备独立维护定时刷新任务,调用after_cancel避免重复刷新,确保每2秒更新自身数据。
  • 异常处理:添加网络请求异常捕获,避免单个设备连接失败导致整个GUI崩溃。

内容的提问来源于stack exchange,提问作者Kuralmozhi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 16:00:53