0

I am creating GUI to show Temperature & Humidity from ip address. This is my mainwindow,

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

class MainApplication(tk.Tk):
def __init__(self):
    super().__init__()
    global button_counter
    global button_position
    button_counter = 0

    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.frame = tk.Frame(self, height=50, width=500)
    self.frame.pack()

    logo_image = Image.open('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=LEFT)

    self.title = tk.Label(self.frame,
                          text="ESP32 Temperature & Humidity Measurement",
                          fg="DodgerBlue2",
                          font='Times 25 bold')
    self.title.pack(side=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.status_var = tk.StringVar()

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

Here whenever I click + symbol it adds devices with id and ip address. This is the code to create another window to add device,

    def add_device(self):
        self.window = tk.Tk()
        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=lambda: 
             [self.add_command(), self.get_ip_data(), 
        self.show_message()])
        self.add_button.place(x=350, y=200)

and whenever I add devices it gets the data from ip and update into a button which will be appeared on the mainscreen. This is the code to create buttons when I add new device,

    def add_command(self):
        global button_counter
        global button_position

        if button_counter > len(button_position):
           return
        x, y = button_position[button_counter]
        self.result_button = tk.Button(self,
                                   text="",
                                   font="Times 15",
                                   bg="SkyBlue1",
                                   fg="black",
                                highlightthickness=2,
                                   command=lambda: 
                                 [self.get_ip_data(),
                                  self.update_data(),
                                self.show_details()])
        self.result_button.place(x=x, y=y)
        button_counter += 1

The code to get data from ip address is,

    def get_ip_data(self):
        id = self.id_entry.get()
        ip = self.ip_entry.get()
        print(f"The id number is {id}")
        print(f"The ip address is {ip}")

        response = requests.get(f"http://{ip}/")
        data = response.json()
        print(data)

        temp = round(data['temperature'], 2)
        humi = round(data['humidity'], 2)
        id_json = data['id']
        print(f"temperature for id {id} is {temp}")
        print(f"humidity for id {id} is {humi}")

        if int(id_json) == int(id):
           self.result_button.config(text=f"Device 
               {id}\nTemperature: {temp°C\n
               Humidity: {humi} %")
        else:
           tk.messagebox.showerror("Error!", "Invalid 
                                    ID")

This is the function to update data after 2 seconds,

    def update_data(self):
        self.after(2000, self.get_ip_data)

and finally this is the code that creates new window to display the temperature and humidity of result.

    def show_details(self):
        self.new_window = tk.Tk()
        self.new_window.geometry("500x500")
        self.new_window.config(bg="LightSkyBlue1")
        self.new_window.resizable(0,0)
        my_text = self.result_button['text']
        self.label = tk.Label(self.new_window,
                          text=my_text,
                          bg="LightSkyBlue1",
                          fg="black",
                          font="Times 20 bold")
        self.label.place(relx=0.5, rely=0.5, anchor=CENTER)
        print(my_text)

My need is whenever I click result_button then new window should be open and show the details of first device. If I add two device means whenever click result_button of second device means that shows the details the second device. My requirement is for example I add 10 devices means if I click device 1 button then it shows the details. Like that each device shows the details of temperature and humidity individually.

My problem is If I click result_button for first device means it shows details of first device but if i click result_button for second device means both device buttons shows same details. And also the data from ip does not update after 2 seconds automatically.

Can anyone suggest me to solve this problem?

Kuralmozhi
  • 47
  • 7
  • You have used same instance variable `self.result_button` for all the device buttons, so `self.result_button` will reference the last added button. You need to use a dictionary or list to store the references of those buttons. – acw1668 Jul 20 '23 at 09:55
  • I created an dictionary and stores the details like **my_dictionary = { 1: id, 2:temp, 3:humi}**, But this is also take the last data I update only! – Kuralmozhi Jul 20 '23 at 10:20
  • Can you help me to solve this issue? – Kuralmozhi Jul 20 '23 at 11:46

1 Answers1

1

The issue is caused by using same instance variable self.result_button for all the device buttons, so it will always reference the last added button.

It is a bit hard to overcome the issue for your current design, so I would suggest to create a custom button to group all the related functions, like get_ip_data(), show_details(), etc., into that class.

Also there are few bad practices you should avoid in your code:

  • using wildcard import
  • multiple instances of Tk(), use Toplevel() for child windows
  • call multiple functions inside lambda
  • using not necessary global variables, button_counter and button_position which can use instance variables instead

Below is the custom button class:

# you can use whatever name you want
class DeviceButton(tk.Button):
    def __init__(self, master, id, ip):
        super().__init__(master, font="Times 15", bg="SkyBlue1", fg="black",
                         highlightthickness=2, command=self.show_details)
        self.id = id
        self.ip = ip
        self.get_ip_data()

    def get_ip_data(self):
        print(f"The id number is {self.id}")
        print(f"The ip address is {self.ip}")

        response = requests.get(f"http://{self.ip}/")
        data = response.json()
        print(data)

        temp = round(data['temperature'], 2)
        humi = round(data['humidity'], 2)
        id_json = data['id']
        print(f"temperature for id {self.id} is {temp}")
        print(f"humidity for id {self.id} is {humi}")

        if int(id_json) == int(self.id):
           self.config(text=f"Device {self.id}\nTemperature: {temp}°C\nHumidity: {humi}%")
        else:
           tk.messagebox.showerror("Error!", f"Invalid ID")

    def show_details(self):
        # use Toplevel instead of Tk
        self.new_window = tk.Toplevel()
        self.new_window.geometry("500x500")
        self.new_window.config(bg="LightSkyBlue1")
        self.new_window.resizable(0,0)
        self.label = tk.Label(self.new_window,
                              text=self["text"],
                              bg="LightSkyBlue1",
                              fg="black",
                              font="Times 20 bold")
        self.label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
        print(self["text"])

Below is the modified MainApplication class to use the new custom button class:

class MainApplication(tk.Tk):
    def __init__(self):
        super().__init__()
        # change to use instance variables instead
        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.frame = tk.Frame(self, height=50, width=500)
        self.frame.pack()

        logo_image = Image.open('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)

        # rename self.title to self.title_label because Tk.title is a function inside the class Tk
        self.title_label = tk.Label(self.frame,
                                    text="ESP32 Temperature & Humidity Measurement",
                                    fg="DodgerBlue2",
                                    font='Times 25 bold')
        self.title_label.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):
        # use tk.Toplevel instead of tk.Tk
        self.window = tk.Toplevel()
        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_command)
        self.add_button.place(x=350, y=200)

    def add_command(self):
        if self.button_counter > len(self.button_position):
           return
        x, y = self.button_position[self.button_counter]
        # use the new custom button class
        result_button = DeviceButton(self, self.id_entry.get(), self.ip_entry.get())
        result_button.place(x=x, y=y)
        self.button_counter += 1

Note that you may need to modify the above classes to cope with your current code.

acw1668
  • 40,144
  • 5
  • 22
  • 34