Python Forum
tkinter toggle buttons not working - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: GUI (https://python-forum.io/forum-10.html)
+--- Thread: tkinter toggle buttons not working (/thread-36081.html)

Pages: 1 2 3


tkinter toggle buttons not working - Nu2Python - Jan-15-2022

Hi All,

I am working on some code in tkinter for creating a small GUI to control relays with a simple toggle on/off button image. For some reason, the toggle buttons are not working properly and I cannot figure out why. I click one button and the other button switches its image. Once I get past this, I will have probably 6-8 buttons for relay controls. Hoping that someone can see an obvious issue here.... Thanks for any help

#!/usr/bin/env python
""" http POST example """

from guizero import App, PushButton, Slider, Text
import time
import requests

ESPR1_Relay_On  = 'http://192.168.0.101/cm?cmnd=Power On'
ESPR1_Relay_Off  = 'http://192.168.0.101/cm?cmnd=Power Off'
SONOFF_Relay01_On  = 'http://192.168.0.113/cm?cmnd=Power On'
SONOFF_Relay01_Off  = 'http://192.168.0.113/cm?cmnd=Power Off'

sonoff_R1_url = 'NOT_INIT'
esp_R1_url = 'NOT_INIT'

#global is_on
on_sonoff_R1 = 0
on_esp_R1 = 0


class button:
    def __init__(self,index,image,grid):
        self.index = index
        self.image = image
        self.grid = grid
        
def sonoff_R1_button(n):
    global on_sonoff_R1
    print('SONOFF R1 STATUS()',n)
    if on_sonoff_R1 == 0:
        on_sonoff_R1 = 1
        pushButtons[n].image = buttons[n].image+'_on.jpg'
        sonoff_R1_url = SONOFF_Relay01_On
        sonR1 = requests.post(sonoff_R1_url)        
    else:
        on_sonoff_R1 = 0
        pushButtons[n].image = buttons[n].image+'_off.jpg'
        sonoff_R1_url = SONOFF_Relay01_Off
        sonR1 = requests.post(sonoff_R1_url)
        
                
def espR1_button(n):
    global on_esp_R1
    print('ESP R1 STATUS()',n)
    if on_esp_R1 == 0:
        on_esp_R1 = 1
        pushButtons[n].image = buttons[n].image+'_on.jpg'
        esp_R1_url = ESPR1_Relay_On
        espR1 = requests.post(esp_R1_url)
    else:
        on_esp_R1 = 0
        pushButtons[n].image = buttons[n].image+'_off.jpg'
        esp_R1_url = ESPR1_Relay_Off
        espR1 = requests.post(esp_R1_url)
     
path = '/home/pi/'

buttons = []
buttons.append(button(0,'living_room',[0,0]))
buttons.append(button(1,'kitchen',[1,0]))

app = App(title="Keypad example", width=1280, height=520, layout="grid")
app.bg='black'

pushButtons=[]
for button in buttons:
    pushButtons.append(PushButton(app, args=[button.index], command=espR1_button, grid=button.grid, align='left', image = path + button.image+'_off.jpg'))
    pushButtons.append(PushButton(app, args=[button.index], command=sonoff_R1_button, grid=button.grid, align='left', image = path + button.image+'_off.jpg'))

 
def main():

    #app.tk.attributes("-fullscreen",True)
    #app.tk.config(cursor='none')
    app.display()

if __name__ == "__main__":
  main()



RE: tkinter toggle buttons not working - deanhystad - Jan-15-2022

I can't see what is wrong. I assume from the print() calls that you are seeing the correct function called with the correct index when a pushbutton is pressed. I don't think it is worth the effort debugging this code. It has already proven difficult to understand and doesn't support multiple buttons well. Time for a redesign.

The main thing you need to do is associate the URL, state and images with the button so when you press the button it knows how to toggle itself without having to depend on any external help. There should be no global variables and no special functions. While at it you might as well make button a proper subclass of Pushbutton instead of being a weird helper class.

I have not used guizero nor requests, so this is really, really untested code.
#!/usr/bin/env python

from guizero import App, PushButton
import time
import requests

path = '/home/pi/'

class RelayButton(PushButton):
    """A button that toggles a relay"""
    def __init__(self, master, url, on_image, off_image, **kwargs):
        super().__init__(master, image=off_image, command=self.toggle())
        self.url = url
        self._on = False
        self.on_image = on_image
        self.off_image = off_image

    @property
    def on(self):
        """Return state of the relay"""
        return self._on

    @on.setter
    def on(self, on):
        """Set state of the relay"""
        self._on = on
        if on:
            requests.post(f"{self.url}cm?cmnd=Power On")
            self.image = self.on_image
        else:
            requests.post(f"{self.url}cm?cmnd=Power Off")
            self.image = self.off_image
 
    def toggle(self):
        """Toggle state of the relay"""
        self.on = not self.on

def new_button(name, url, master, **kwargs):
    """Convenience functon that creates a RelayButton"""
    return RelayButton(master, url, f"{path}{name}_on.jpg", f"{path}{name}_off.jpg", **kwargs)

def main():
    app = App(title="Keypad example", width=1280, height=520, layout="grid")
    app.bg='black'
    buttons = [
        new_button('living_room', "http://192.168.0.101/", app, grid=(0, 0)),
        new_button('kitchen', "http://192.168.0.113/", app, grid=(0, 1))
    ]
    for b in buttons:
        b.on = False
    app.display()
 
if __name__ == "__main__":
  main()
RelayButton is a subclass of PushButton that toggles its image and a relay when pressed. All the information needed to do this is provided as arguments to the __init__() method.


RE: tkinter toggle buttons not working - Yoriz - Jan-15-2022

There is an example of toggling an image directly in tkinter here


RE: tkinter toggle buttons not working - Nu2Python - Jan-15-2022

Thanks deanhystad.. I am trying to go through your code to better understand it, but so far this is the error that I am getting on first test:

Traceback (most recent call last):
  File "relay-2.py", line 54, in <module>
    main()
  File "relay-2.py", line 46, in main
    new_button('living_room', "http://192.168.0.101/", app, grid=(0, 0)),
  File "relay-2.py", line 40, in new_button
    return RelayButton(master, url, f"{path}{name}_on.jpg", f"{path}{name}_off.jpg", **kwargs)
  File "relay-2.py", line 12, in __init__
    super().__init__(self, master, image=off_image, command=self.toggle())
  File "relay-2.py", line 36, in toggle
    self.on = not self.on
  File "relay-2.py", line 21, in on
    return self._on
AttributeError: 'RelayButton' object has no attribute '_on'
(Jan-15-2022, 02:32 PM)deanhystad Wrote: I can't see what is wrong. I assume from the print() calls that you are seeing the correct function called with the correct index when a pushbutton is pressed. I don't think it is worth the effort debugging this code. It has already proven difficult to understand and doesn't support multiple buttons well. Time for a redesign.

The main thing you need to do is associate the URL, state and images with the button so when you press the button it knows how to toggle itself without having to depend on any external help. There should be no global variables and no special functions. While at it you might as well make button a proper subclass of Pushbutton instead of being a weird helper class.

I have not used guizero nor requests, so this is really, really untested code.
#!/usr/bin/env python

from guizero import App, PushButton
import time
import requests

path = '/home/pi/'

class RelayButton(PushButton):
    """A button that toggles a relay"""
    def __init__(self, master, url, on_image, off_image, **kwargs):
        super().__init__(self, master, image=off_image, command=self.toggle())
        self.url = url
        self._on = False
        self.on_image = on_image
        self.off_image = off_image

    @property
    def on(self):
        """Return state of the relay"""
        return self._on

    @on.setter
    def on(self, on):
        """Set state of the relay"""
        self._on = on
        if on:
            requests.post(f"{self.url}cm?cmnd=Power On")
            self.image = self.on_image
        else:
            requests.post(f"{self.url}cm?cmnd=Power Off")
            self.image = self.off_image
 
    def toggle(self):
        """Toggle state of the relay"""
        self.on = not self.on

def new_button(name, url, master, **kwargs):
    """Convenience functon that creates a RelayButton"""
    return RelayButton(master, url, f"{path}{name}_on.jpg", f"{path}{name}_off.jpg", **kwargs)

def main():
    app = App(title="Keypad example", width=1280, height=520, layout="grid")
    app.bg='black'
    buttons = [
        new_button('living_room', "http://192.168.0.101/", app, grid=(0, 0)),
        new_button('kitchen', "http://192.168.0.113/", app, grid=(0, 1))
    ]
    for b in buttons:
        b.on = False
    app.display()
 
if __name__ == "__main__":
  main()
RelayButton is a subclass of PushButton that toggles its image and a relay when pressed. All the information needed to do this is provided as arguments to the __init__() method.



RE: tkinter toggle buttons not working - deanhystad - Jan-15-2022

The error is caused by calling super().__init__(self, master...). This was calling PushButton.__init__(self, self, master...) which resulted in trying to create a new PushButton using "self" as the parent window. The correct call is super().__init__(master,...). This is some code that I tested using tkinter. Surprisingly few changes were required.

Why are you using guizero instead of tkinter?
import tkinter as tk
 
path = ""
 
class RelayButton(tk.Button):
    """A button that toggles a relay"""
    def __init__(self, parent, url="", on_image=None, off_image=None, **kwargs):
        super().__init__(parent, image=off_image, command=self.toggle)
        self.url = url
        self._on = False
        self.on_image = on_image
        self.off_image = off_image
        if "grid" in kwargs:
            row, column = kwargs.pop("grid")
            self.grid(row=row, column=column)
 
    @property
    def on(self):
        """Return state of the relay"""
        return self._on
 
    @on.setter
    def on(self, on):
        """Set state of the relay"""
        self._on = on
        if on:
            # requests.post(f"{self.url}cm?cmnd=Power On")
            self["image"] = self.on_image
        else:
            # requests.post(f"{self.url}cm?cmnd=Power Off")
            self["image"] = self.off_image
  
    def toggle(self):
        """Toggle state of the relay"""
        self.on = not self.on
 
def new_button(name, url, parent, **kwargs):
    """Convenience functon that creates a RelayButton"""
    on_image = tk.PhotoImage(file=f"{path}{name}_on.png")
    off_image = tk.PhotoImage(file=f"{path}{name}_off.png")
    return RelayButton(parent, url, on_image, off_image, **kwargs)
 
def main():
    app = tk.Tk()
    buttons = [
        new_button('living_room', "http://192.168.0.101/", app, grid=(0, 0)),
        new_button('kitchen', "http://192.168.0.113/", app, grid=(0, 1))
    ]
    for b in buttons:
        b.on = False
    app.mainloop()
  
if __name__ == "__main__":
  main()



RE: tkinter toggle buttons not working - Nu2Python - Jan-15-2022

Thank you deanhystad... you made my day. This works great. Now I can start creating the rest of this. You also solved the issue for me to know if the relays are on or off in the event the program shuts off etc.

I really had no specific reason for using guizero as I usually do use tkinter. I was just playing around with some code that I found that happened to use guizero so I stuck with it, but I like this better anyway. Any other ideas you have for relay control, please feel free to send it my way as this has so far been a fun project. Thanks again for your help and your time, I really appreciate it.


RE: tkinter toggle buttons not working - deanhystad - Jan-15-2022

Is there a way to query the current relay state? Turning off all the relays just because you started the program is rude. If there is a query that will return the state I would add that to the __init__() method and use that info to initialize _on and set the initial button image.


RE: tkinter toggle buttons not working - Nu2Python - Jan-15-2022

That is what I have been looking for, a way to find out which state the relays are in if something was powered off for some reason. I have been looking at the http.client to get a connection request. Right now I am uncertain how to implement it.


RE: tkinter toggle buttons not working - deanhystad - Jan-15-2022

Can you post a link to the relay api?


RE: tkinter toggle buttons not working - Nu2Python - Jan-16-2022

Let me see what I can find for this. I am using tasmota to control a Sonoff relay and a basic ESP8266 relay. https://tasmota.github.io/docs/Commands/

I am looking at some commands like this:
r = requests.get('http://192.168.0.101/control?cmnd=Status,gpio,14')
print(r.status_code)
It just returns a code 404 which seems to be like a "No Response". So for this I was trying to check the status of the LED that is onboard the Relay. Not much luck here so far.

I was also trying to figure out how to put space between the buttons. I tried using "padx=20", but it seems to ignore it.
def main():
    app = tk.Tk()
    buttons = [
        new_button('living_room', "http://192.168.0.101/", app, grid=(0, 0), padx=20),
        new_button('front_porch', "http://192.168.0.113/", app, grid=(0, 1), padx=20)
    ]