My daughter gave me the LED flashing Xmas tree HAT for RPi for XMas!
I have set up a number of continuous flashing programs under control of a tkinter button interface. This works fine, but I want to be able to switch the programs with a button click; currently each of the programs run until completion (e.g. five cycles of the flashing combination). Rather I would like to leave the thing flashing on one program, and change to another on a button click.
Is there a way to interrupt a flashing program by a button click on a different button??
ChatGPT says I have to use threading!? This would be an entirely new skill, and looks challengingā¦.
Thoughts?
Hey @David197028,
Would it be acceptable to have the program change after it finishes the current loop? Adding interrupt checks to the code would be how I would handle this. This would introduce a small but hopefully not noticeable delay and is much simpler than implementing threading.
Would you be able to share the current code you are using or link to it so we can get a better idea of the programs you are wanting to switch between?
Sure - Iāve just rejigged it to create a new object-oriented format, but the annotations should be clear enough.
Thanks, David
import tkinter
import threading
import time
import random
from colorzero import Color, Hue
from tree import RGBXmasTree
COLOURS = [Color('green'), Color('red'), Color('blue'), Color('orange')]
window = tkinter.Tk()
class FlashingProgram:
def __init__(self, name, present_colour):
self.name = name
self.running = False
self.present_colour = present_colour
def cycle(self):
global tree
#get the next colour
colour_now = COLOURS.index(self.present_colour)
colour_now += 1
if colour_now == 4:
colour_now = 0
self.present_colour = COLOURS[colour_now]
tree.color = self.present_colour
step = 1
bright=0
for counter in range (0,21): #fluctuates the brightness once
tree.brightness = bright/10
bright += step
if bright >10:
step = -step
bright = 9
elif bright <0:
step = -step
bright = 0
time.sleep(0.01)
def random_sparkles(self):
global tree
tree.brightness = 0.1
pixel = random.choice(tree)
r = random.random()
g = random.random()
b = random.random()
pixel.color = (r, g, b)
def random_colours(self):
global tree
tree.brightness = 0.1
pixel = random.choice(tree)
pixel.color = random.choice(COLOURS)
def hue_cycle(self):
global tree
tree.brightness = 0.1
self.present_colour += Hue(deg=1)
tree.color = self.present_colour
def start(self):
self.running = True
while self.running:
if self.name == "cycle":
#do one interation of the flassh program:
self.cycle()
#check if button has been clicked; if so, return
if self.name == "random_sparkles":
#do one interation of the flassh program:
self.random_sparkles()
#check if button has been clicked; if so, return
if self.name == "random_colours":
#do one interation of the flassh program:
self.random_colours()
#check if button has been clicked; if so, return
if self.name == "hue_cycle":
#do one interation of the flassh program:
self.hue_cycle()
#check if button has been clicked; if so, return
else:
return
def set_flag():
#Maybe here would go an interrupt flag?
#Probably to take a different arg from each button?
flash_state_lbl.config(text = "Clicked")
print("Clicked")
window.update()
tree = RGBXmasTree()
#Initiate the various flashing sequence objects
flasher1 = FlashingProgram("cycle", Color('red'))
flasher2 = FlashingProgram("random_sparkles", None)
flasher3 = FlashingProgram("random_colours", None)
flasher4 = FlashingProgram("hue_cycle", Color('red'))
#Buttons setup
cycle_btn = tkinter.Button(window, text="Cycle", command=set_flag)
cycle_btn.grid(row=0, column=0)
random_colours_btn = tkinter.Button(window, text="Rndom colours", command = set_flag)
random_colours_btn.grid(row=0, column=1)
hue_cycle_btn = tkinter.Button(window, text="Hue cycle", command=set_flag)
hue_cycle_btn.grid(row=0, column=2)
random_sparkles_btn = tkinter.Button(window, text="Random sparkles", command=set_flag)
random_sparkles_btn.grid(row=0, column=3)
#Label setup
flash_state_lbl = tkinter.Label(window, text="Off")
flash_state_lbl.grid(row=1, column=0, columnspan=4)
window.update()
try:
flasher3.start()
except KeyboardInterrupt:
tree.off()
tree.close()
window.mainloop()
Hey @David197028,
I would implement a global āhas the button been pressedā variable and put an if statement check for this inside of the for loop in the cycle function.
This will give you some kind of a delay but this way it will only be the delay associated with one brightness fluctuation cycle instead of the entire functions duration.
I think that would work for a physical button, but Iām not sure how to implement if for a tkinter button. Whilst the loop is continuously looping (including checking for a button press), it is not monitoring the buttons to change the state of the āhas the button been pressedā variable.
For a tkinter button the procedure is the same, except that the global variable is set in the method you construct for the button to call into whenever the button press is detected. Using the example provided, it would look something like this:
from tkinter import *
top = Tk()
pressed = false
top.geometry("100x100")
def helloCallBack():
pressed = True
B = Button(top, text ="Hello", command = helloCallBack)
B.place(x=50,y=50)
top.mainloop()
...
if pressed = True:
[change the pattern]
pressed = False
...
Thanks Jeff. I gave that a go, but while the program is busy running the flashing cycle, the button press is not registered and the āpressedā variable (equivalent) is not changed, so I canāt find a way to exit the loop.
Hi @David197028,
If you are feeling up to attempting this project using threading the following code should work as a scaffold to set up your lighting functions so that they change quickly on the button press.
import tkinter
import threading
import time
import random
from colorzero import Color, Hue
from tree import RGBXmasTree
COLOURS = [Color('green'), Color('red'), Color('blue'), Color('orange')]
window = tkinter.Tk()
class FlashingProgram:
def __init__(self, name, present_colour=None):
self.name = name
self.running = False
self.present_colour = present_colour
self.thread = None
def start(self):
self.running = True
self.thread = threading.Thread(target=self.run)
self.thread.start()
def stop(self):
self.running = False
if self.thread is not None:
self.thread.join()
def run(self):
while self.running:
if self.name == "cycle":
self.cycle()
elif self.name == "random_sparkles":
self.random_sparkles()
elif self.name == "random_colours":
self.random_colours()
elif self.name == "hue_cycle":
self.hue_cycle()
self.thread = None
def cycle(self):
global tree
# Lighting changes for cycle
time.sleep(0.01)
def random_sparkles(self):
global tree
# Lighting changes for random_sparkles
time.sleep(0.01)
def random_colours(self):
global tree
# Lighting changes for random_colours
time.sleep(0.01)
def hue_cycle(self):
global tree
# Lighting changes for hue_cycle
time.sleep(0.01)
def set_flag(flasher):
global running_flasher
if running_flasher:
running_flasher.stop()
flasher.start()
running_flasher = flasher
tree = RGBXmasTree()
running_flasher = None
flasher1 = FlashingProgram("cycle", Color('red'))
flasher2 = FlashingProgram("random_sparkles")
flasher3 = FlashingProgram("random_colours")
flasher4 = FlashingProgram("hue_cycle", Color('red'))
cycle_btn = tkinter.Button(window, text="Cycle", command=lambda: set_flag(flasher1))
cycle_btn.grid(row=0, column=0)
random_colours_btn = tkinter.Button(window, text="Random colours", command=lambda: set_flag(flasher2))
random_colours_btn.grid(row=0, column=1)
hue_cycle_btn = tkinter.Button(window, text="Hue cycle", command=lambda: set_flag(flasher3))
hue_cycle_btn.grid(row=0, column=2)
random_sparkles_btn = tkinter.Button(window, text="Random sparkles", command=lambda: set_flag(flasher4))
random_sparkles_btn.grid(row=0, column=3)
flash_state_lbl = tkinter.Label(window, text="Off")
flash_state_lbl.grid(row=1, column=0, columnspan=4)
try:
window.mainloop()
finally:
if running_flasher:
running_flasher.stop()
tree.off()
tree.close()
I havenāt been able to test this code with a physical setup that matches yours so it may need some modification to work for you. I also havenāt added any particular lighting patterns into the flashing programs.
Hopefully, this gives you some idea of how threading could be implemented into this project!
Let me know how you go or if this code gives you any weird errors.
Have you determined whether the problem is that the press is not registered, or the variable is not tested? To confirm that the button press is registered you would put a message box in helloCallBack. To determine the state of the variable you would print it at the start of the loop.
(I presume you noted my syntax error - if Iām not typing semicolons I regress )
Sam, that is so brilliant! Thanks - Iāll have to spend hours working through how it works, but it absolutely works brilliantly.
Have made a few cosmetic changes, but for any interested, hereās the final code:
import tkinter
import threading
import time
import random
from colorzero import Color, Hue
from tree import RGBXmasTree
COLOURS = [Color('green'), Color('red'), Color('blue'), Color('orange')]
BTN_WIDTH=15
BTN_HEIGHT=2
FONT=("Arial", 18, "bold")
window = tkinter.Tk()
window.title("Christmas tree lights")
window.geometry("900x600")
class FlashingProgram:
def __init__(self, name, present_colour=None):
self.name = name
self.running = False
self.present_colour = present_colour
self.thread = None
def start(self):
self.running = True
self.thread = threading.Thread(target=self.run)
self.thread.start()
def stop(self):
self.running = False
if self.thread is not None:
self.thread.join()
def run(self):
while self.running:
if self.name == "cycle":
self.cycle()
elif self.name == "random_sparkles":
self.random_sparkles()
elif self.name == "random_colours":
self.random_colours()
elif self.name == "hue_cycle":
self.hue_cycle()
elif self.name=="Off":
pass #tree has already been turned off
self.thread = None
def cycle(self):
global tree
#get the next colour
colour_now = COLOURS.index(self.present_colour)
colour_now += 1
if colour_now == 4:
colour_now = 0
self.present_colour = COLOURS[colour_now]
tree.color = self.present_colour
step = 1
bright=0
for counter in range (0,21): #fluctuates the brightness once
tree.brightness = bright/10
bright += step
if bright >10:
step = -step
bright = 9
elif bright <0:
step = -step
bright = 0
time.sleep(0.01)
def random_sparkles(self):
global tree
tree.brightness = 0.1
pixel = random.choice(tree)
r = random.random()
g = random.random()
b = random.random()
pixel.color = (r, g, b)
def random_colours(self):
global tree
tree.brightness = 0.1
pixel = random.choice(tree)
pixel.color = random.choice(COLOURS)
def hue_cycle(self):
global tree
tree.brightness = 0.1
self.present_colour += Hue(deg=1)
tree.color = self.present_colour
def set_flag(flasher):
global running_flasher
if running_flasher:
running_flasher.stop()
#Clear tree lights
global tree
tree.color = (0, 0, 0)
#Update label
if flasher==flasher1:
label_txt = "Cycle"
elif flasher==flasher2:
label_txt = "Random colours"
elif flasher==flasher3:
label_txt = "Random sparkles"
elif flasher==flasher4:
label_txt = "Hue cycle"
elif flasher==flasher0:
label_txt = "Off"
flash_state_lbl.config(text=label_txt)
#Start flasher
flasher.start()
running_flasher = flasher
tree = RGBXmasTree()
running_flasher = None
flasher0 = FlashingProgram("Off")
flasher1 = FlashingProgram("cycle", Color('red'))
flasher2 = FlashingProgram("random_colours")
flasher3 = FlashingProgram("random_sparkles")
flasher4 = FlashingProgram("hue_cycle", Color('red'))
off_btn = tkinter.Button(window, text="Off", width=BTN_WIDTH, height=BTN_HEIGHT, font=FONT, command=lambda: set_flag(flasher0))
off_btn.grid(row=0, column=0)
cycle_btn = tkinter.Button(window, text="Cycle", width=BTN_WIDTH, height=BTN_HEIGHT, font=FONT, command=lambda: set_flag(flasher1))
cycle_btn.grid(row=1, column=0)
random_colours_btn = tkinter.Button(window, text="Random colours", width=BTN_WIDTH, height=BTN_HEIGHT,font=FONT, command=lambda: set_flag(flasher2))
random_colours_btn.grid(row=1, column=1)
hue_cycle_btn = tkinter.Button(window, text="Hue cycle", width=BTN_WIDTH, height=BTN_HEIGHT,font=FONT, command=lambda: set_flag(flasher4))
hue_cycle_btn.grid(row=1, column=2)
random_sparkles_btn = tkinter.Button(window, text="Random sparkles", width=BTN_WIDTH, height=BTN_HEIGHT,font=FONT, command=lambda: set_flag(flasher3))
random_sparkles_btn.grid(row=1, column=3)
flash_state_lbl = tkinter.Label(window, text="Off", width=BTN_WIDTH, height=BTN_HEIGHT, font=FONT)
flash_state_lbl.grid(row=2, column=0, columnspan=4)
try:
window.mainloop()
finally:
if running_flasher:
running_flasher.stop()
tree.off()
tree.close()
Hi @David197028
Great to hear that you got it all working!