capovawi
capovawi

Reputation: 375

LED to blink when a push button is NOT pressed and to be continually on when the button IS pressed (Python)

So, i have a LED controlled by a Raspy, and also a basic GUI with just a push button, I want it to behave as follows:

When I execute light starts blinking, but the button does not show up. When I interrupt the program, the button shows up. Why?

from tkinter import *
import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BOARD)
GPIO.setup(8, GPIO.OUT)
GPIO.output(8, False)
buttonPressed = False

master = Tk()

def callback():
    GPIO.output(8, True)
    buttonPressed = True

w = Button(master, text="Turn on light", command = callback)
w.pack()

while True:
    if buttonPressed == False:
        GPIO.output(8, True)
        time.sleep(0.5)
        GPIO.output(8, False)
        time.sleep(0.5)
    else:
        GPIO.output(8, True)

Upvotes: 1

Views: 1601

Answers (3)

acw1668
acw1668

Reputation: 46669

A tkinter application needs to call .mainloop().

Also you should use .after(...) function to simulate the while loop:

import tkinter as tk
import RPi.GPIO as GPIO

def led_on(state):
    global button_pressed
    button_pressed = state

def blink_led(state=True):
    # turn on LED if either state or button_pressed is True
    GPIO.output(8, state or button_pressed)
    master.after(500, blink_led, not state) # toggle the state half a second later

# setup the RPi board
GPIO.setmode(GPIO.BOARD)
GPIO.setup(8, GPIO.OUT)
GPIO.output(8, False)

button_pressed = False

master = tk.Tk()

w = tk.Button(master, text='Turn on light')
w.pack()

w.bind('<ButtonPress-1>', lambda e: led_on(True))  # set button_pressed to True
w.bind('<ButtonRelease-1>', lambda e: led_on(False)) # set button_pressed to False

blink_led()  # start the LED blinking

master.mainloop()

Upvotes: 3

furas
furas

Reputation: 142631

I can't test it on RPi but I could be something like this.

Normal command= can recognize only when you clicked button but it can't recognize when you released it - you need to bind events <ButtonPress> and <ButtonRelease> which will run functions on "mouse left button press" and "mouse left button release"

I use after() to run function with delay so I don't need sleep() and while which can block mainloop() (and it can freeze all GUI).

And I don't need also while loop because I run all inside on_press and on_release and later after() runs turn_off_led which use after() to run turn_on_led which use after() to run again turn_off_led so it works like loop.

import tkinter as tk  # PEP8: `import *` is not preferred
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BOARD)
GPIO.setup(8, GPIO.OUT)
GPIO.output(8, False)

button_pressed = False  # PEP8: preferred `lower_case_names`

# --- functions ---

def on_press(event):
    global button_pressed

    button_pressed = True
    GPIO.output(8, True)

def on_release(event):
    global button_pressed

    button_pressed = False
    GPIO.output(8, True)

    # run after 500ms (0.5s) instead of `sleep`
    master.after(500, blink_off)

def blink_off():
    if not button_pressed:
        GPIO.output(8, False)
        # run after 500ms (0.5s) instead of `sleep`
        master.after(500, blink_on)

def blink_on():
    if not button_pressed:
        GPIO.output(8, True)
        # run after 500ms (0.5s) instead of `sleep`
        master.after(500, blink_off)

# --- main ---

master = tk.Tk()

button = tk.Button(master, text="Turn on light")
button.pack()

# here "button" means "tk.Button" and "Button" means "mouse left button"
button.bind('<ButtonPress>',   on_press)   # mouse left button pressed on tk.Button 
button.bind('<ButtonRelease>', on_release) # mouse left button released on tk.Button

# start blinking - it will use `after()` to loop
blink_on()

master.mainloop()

Upvotes: 2

larsks
larsks

Reputation: 311238

I think that @furas has you set in the right direction, but since I was working on it for the fun of it I thought I would leave my solution here as well. I pulled out the GPIO code so that I could run it locally.

A key feature of both solutions is that we've removed the calls to time.sleep, because when you're time.sleeping, Tk's event loop isn't able to process any events (which would manifest as the UI appearing to "freeze" during those sleep statements).

import time
import tkinter

buttonpressed = False
lastchange = 0
ledstate = False

def button_down(event):
    global buttonpressed
    print('BUTTON DOWN')
    buttonpressed = True


def button_up(event):
    global buttonpressed
    print('BUTTON UP')
    buttonpressed = False


def myloop(master):
    global buttonpressed
    global lastchange
    global ledstate

    now = time.time()
    delta = now - lastchange

    if not buttonpressed:
        if now - lastchange > 0.5:
            ledstate = not ledstate
            print('LED', ledstate)
            lastchange = now
    else:
        if not ledstate:
            ledstate = True
            print('LED', ledstate)

    master.after_idle(myloop, master)


master = tkinter.Tk()
w = tkinter.Button(master, text="Turn on light")
w.bind('<ButtonPress>', button_down)
w.bind('<ButtonRelease>', button_up)
w.pack()

master.after(100, myloop, master)
master.mainloop()

Upvotes: 3

Related Questions