Search code examples
python-3.xbuttoncallbackkivyraspberry-pi3

Bind a button to a callback from Kivy to Python


I've been banging my head on this for days now. I made it work in Python only (not using the .kv file) but now I just can't figure this out.

Basically, I'll have a series of button to control GPIOs on a Raspberry Pi. In the Python only version, I have one call back with a series of IFs looking for the right button "text" to determine which pin to activate. Now in the kivy version, I dumbed it down to a single button, moved the callback to many places in the code, etc. but nothing seems to work.

Here's the Python code:

# Set up GPIO:
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
defrost = 27
GPIO.setup(defrost, GPIO.OUT)
GPIO.output(defrost, GPIO.LOW)


class ConsoleUI(BoxLayout):
    def press_callback(obj):

            if obj.state == "down":
               GPIO.output(defrost, GPIO.HIGH)
            else:
               GPIO.output(defrost, GPIO.LOW)

    pass

class ConsolesimpleApp(App):
    def build(self):
        return ConsoleUI()
   

if __name__ == '__main__':
    ConsolesimpleApp().run()

GPIO.cleanup()

Here's the KV code:

# Console.kv


<ConsoleUI>:

    ToggleButton:
        id: Defrost
        on_press: root.press_callback() 
        Image:
            source: 'rear-window-defrost.png'

And the current error message:

AttributeError: 'ConsoleUI' object has no attribute 'state'

Thank you for any pointers you can give me!

Bonus question: is it better form to have a distinct callback for each button or is the "if obj.text ==" standard? Since I won't have text on my buttons...


Solution

  • The problem is, what you really are doing in press_callback(obj) is checking for state of the BoxLayout (ConsoleUI), since obj in this case is the same as saying self. And as you see, boxlayout does not have any state attribute.
    So what you really want to do, is check for the state of the togglebutton. Luckily you allready gave it an id, so try change your method like this:

    def press_callback(self):
    
            if self.ids.Defrost.state == "down":
               GPIO.output(defrost, GPIO.HIGH)
            else:
               GPIO.output(defrost, GPIO.LOW)
    

    And to answer your bonus question.
    You can pass stuff to the callback. So I would pass the button itself, and the pin number. Now you can actually forget what I said above.
    Try run this little example, to see what I mean:

    from kivy.app import App
    from kivy.lang import Builder
    from kivy.uix.boxlayout import BoxLayout
    
    
    Builder.load_string('''
    
    <ConsoleUI>:
    
        ToggleButton:
            on_press: root.press_callback(self,1)
    
        ToggleButton:
            on_press: root.press_callback(self,2)
    
        ToggleButton:
            on_press: root.press_callback(self,3)
    
    ''')
    
    
    class ConsoleUI(BoxLayout):
    
        def press_callback(self,button,pin):
                if button.state == "down":
                   print("pin {} goes high".format(pin))
                else:
                   print("pin {} goes low".format(pin))
    
    
    
    class ConsolesimpleApp(App):
        def build(self):
            return ConsoleUI()
    
    
    if __name__ == '__main__':
        ConsolesimpleApp().run()