Search code examples
pythonbuttongpiozero

How to use gpiozero.Button.when_pressed within a class or function


I'm working on a weather station (raspberry pi) and I'm using python. I developed a program that is working, but now I decided to update my code and to structure everything (making classes etc.). But now I have encountered two problems concerning Button from gpiozero.
Firstly, when I try to assign a function to Button.when_pressed within a function or class, the function won't get called when the Button gets pressed.

Following code is just an example to keep the code as little as possible:

from gpiozero import Button
import time

number = 0
def function_that_gets_called():
    global number
    number += 1

def main():
    wind_sensor = Button(6)
    wind_sensor.when_pressed = function_that_gets_called

main()

while True:
    # Sleep, so the program doesn't exit immediately
    time.sleep(60)
    

# Do some other stuff with number

Why doesn't function_that_gets_called get called when the Button is pressed. Similarly, when I try to assign when_pressed in a class, it won't work neither.

Secondly, why do I have to use global number? Otherwise the number variable won't get changed, but is there another solution to do it in a better way?

Thank you a lot!

EDIT: Edited time.sleep(60). And I specified, what I mean by doesn't work. This is my first question here on stackoverflow, so please excuse if haven't been precise and it would be great to tell me how to improve my question.


Solution

  • I'm not sure what happens to the wind_sensor when the main funtion exits after you call main(). My guess is that it will be destroyed. Anyway if you remove the main and the call to it, your code shall work. Try add a print(number) after you increment it so you can see something when you press the button

    About classes for this...

    I also struggled with this when_pressed inside a class. But I've made it work in my case. My application can have a variable number of buttons and they are all managed with an object that is instantiated on the beginning of the code. The application doesn't exit because has an underlying websocket client with ws.runForever(). In your case you can use a loop like while 1: pass that shall use some 12% to 15% of your CPU or pause() to keep the code running, without using processor (exit with ctrl+c)

    Now I used a dict to handle the variable nº of buttons that sits inside my class, as well as my handler. Something like this can use classes, keep running and you can expand your code to have a dynamic number of buttons.

    from gpiozero import Button
    import json
    
    class myController():
       theButtons={}
       yourNumber=0
    
       def __init__(self):
          self.yourNumber=0 #here is the right place to initialize the variables!
    
       def handler(self,Button): # here the first parameter is the class itself and then the button that generated the interrupt --> this works like that, but could not confirm that self needs to exist before the mandatory arg "Button" because it is inside the class, or by other reason. Can somebody confirm? not clear in documentation.
          print("Pressed button in pin: ",str(Button.pin.number))
          self.yourNumber +=1
       
       def addButton(self,msg): # Example json: '[{"id":"button1","pin":2},{"id":"button2","pin":4}]' the msg is the message that is received via websocket with the configuration of buttons. you can make it at you own needs. id and pin are string that I choose for machines dialog
          btns=json.loads(msg)
          for b in btns:
             self.theButtons[b.get('id')]=Button(b.get('pin'))
             self.theButtons[b.get('id')].when_pressed=self.handler 
    
    #and that's it for the class no let's instantiate and make the code persist running
    
    if __name__ == "__main__": # this is executed only if you call this file. if you use it as import it will not run. it's a best practice, should you need to import the class in other file and don't want the code to execute. 
       btnController=myController() 
       pause()
    
    

    Classes are a bit overkill on this usecase, but I believe it solves your problem without using global variables.