Search code examples
pythonwhile-looposcneopixel

Exiting a while loop when a global variable changes


So, I've been working on an LED strip connected to my raspberry pi 4, and I've got TouchOSC working between the Pi and my phone. The current problem I'm having is when a toggle button I've pressed is turned off, the program it's designated to run continues running. I have a global variable that's supposed to determine if the "while" loop continues, but instead of setting the button state to zero and terminating the program, it continues to run until it's interrupted by a Ctrl+C in the terminal. I was wondering if anyone would happen to know why the program doesn't stop when the button state is change.

def twinkleBtn(path, tags, args, source):
    global twinkleState
    twinkleState = int(args[0])
    if twinkleState:
       turnOff(strip)
       while twinkleState:
            twinkleTest(strip, False, 10)
    if not twinkleState:
       turnOff(strip)

This is triggered when the twinkle button is pressed, but the program continues to run when I toggle it back to zero. Below is the code to the function "twinkleTest"

def twinkleTest(strip, onlyOne, count, wait_ms=50):
    setPixels(Color(0, 0, 0))
    for i in range(count):
        strip.setPixelColor(randint(0,LED_COUNT), Color(170, 180, 30))
        strip.show()
        time.sleep(wait_ms/250.0)
        if (onlyOne):
           setPixels(Color(0,0,0))
    time.sleep(wait_ms/500.0)

I'm not sure if I'm just clueless or what exactly is being done wrong here. I'm pretty new to Python so it may not be the best. Thanks for any help!


Solution

  • Here is a simple threading solution. A thread waits on a threading.Event. twinkleTest is called whenever the event is set. The GUI sets the event, and twinkling will happen in the background until the button is pressed a second time to stop the twinkling.

    import threading
    
    def twinkleTest(strip, onlyOne, count, wait_ms=50):
        setPixels(Color(0, 0, 0))
        for i in range(count):
            strip.setPixelColor(randint(0,LED_COUNT), Color(170, 180, 30))
            strip.show()
            time.sleep(wait_ms/250.0)
            if (onlyOne):
            setPixels(Color(0,0,0))
        time.sleep(wait_ms/500.0)
    
    def twinkler(twinkleEvent):
        """Call twinkleTest whenever the given twinkleEvent event is set
        and keeps calling until the event is cleared. Designed to be used in
        a separate thread."""
        while True:
            twinkleEvent.wait()
            twinkleTest(strip, False, 10)
    
    def setup_twinkle_daemon_thread():
        """Creates a daemon thread to twinkle screen whenever an event is set.
        Returns event, threadhandle"""
        twinkleEvent = threading.Event()
        twinkleThread = threading.Thread(target=twinker, args=tuple(twinkleEvent))
        twinkleThread.daemon = True
        twinkleThread.start()
        return twinkleEvent, twinkleThread
    
    # controls twinkler
    twinkleEvent = twinkleThread = None
    
    def twinkleBtn(path, tags, args, source):
        global twinkleEvent, twinkleThread
        if twinkleEvent is None:
            # start daemon thread on first use
            twinkleEvent, twinkleThread = setup_twinkle_daemon_thread()
        if twinkleEvent.is_set():
            twinkleEvent.clear()
        else:
            twinkleEvent.set()