Search code examples
pythonmultithreadingwebiopi

Exiting a continuous loop in python via webiopi


I'm using webiopi, and what I basically want to happen is that when the webpage loads, an idle loop which chases some LEDs runs. Then, when someone presses a button on the website, it will stop the idle loop.

Here's what I have so far:

import webiopi
import time

GPIO = webiopi.GPIO

LIGHT1 = 2
LIGHT2 = 3
LIGHT3 = 4

def setup():
    GPIO.setFunction(LIGHT1, GPIO.OUT)
    GPIO.setFunction(LIGHT2, GPIO.OUT)
    GPIO.setFunction(LIGHT3, GPIO.OUT)

a=0

def loop():
    webiopi.sleep(1)

@webiopi.macro
def stopLoop():
    print("Stopping Loop");
    global a
    a = 1
    return a

@webiopi.macro
def idleLoop():
    print("Entering idleLoop");
    while (a==0):
        GPIO.digitalWrite(LIGHT1, GPIO.HIGH)
        time.sleep(0.05)
        GPIO.digitalWrite(LIGHT2, GPIO.HIGH)
        GPIO.digitalWrite(LIGHT1, GPIO.LOW)
        time.sleep(0.05)
        GPIO.digitalWrite(LIGHT3, GPIO.HIGH)
        GPIO.digitalWrite(LIGHT2, GPIO.LOW)
        time.sleep(0.05)

So, I can get it to run the idleLoop, and I have a button hooked up to send the command for stopLoop, and I can see that it goes via POST, however in my debugging window on the PI, I just see it enter idleLoop, but it never enters stopLoop. I'm not sure if I have to write an interrupt, or multithreading, but I just need some guidance. Thanks!


Solution

  • You're in a single threaded application.. which means that once you enter idleLoop(), the machine (or, at least, your program) cannot do anything else, including process your web request to exit the idle loop, because it is stuck blinking your LEDs and sleeping.

    The way to handle this is to have your functions that start and stop the LEDs merely set a global variable then exit.. eg

    blink_enabled = True or blink_enabled = False.

    Something like:

    blink_enabled = False
    
    def loop():
        webiopi.sleep(1)
        if blink_enabled:
            blink_once()
    
    def blink_once():
        GPIO.digitalWrite(LIGHT1, GPIO.HIGH)
        time.sleep(0.05)
        GPIO.digitalWrite(LIGHT2, GPIO.HIGH)
        GPIO.digitalWrite(LIGHT1, GPIO.LOW)
        time.sleep(0.05)
        GPIO.digitalWrite(LIGHT3, GPIO.HIGH)
        GPIO.digitalWrite(LIGHT2, GPIO.LOW)
        time.sleep(0.05)
    
    @webiopi.macro
    def stopLoop():
        blink_enabled = False
    
    @webiopi.macro
    def idleLoop():
        blink_enabled = True
    

    This will allow other tasks, such as handling web requests, to be processed between each blink. During each blink, processing of other things will be blocked for a little bit.. which makes this a less-than-ideal bit of code.. but it shows you the right direction to head in. In a cooperative multitasked environment such as this one, you can never block the loop, you must finish your task as soon as possible and return. If you don't, everything else waits.

    The way the code looks, it SEEMS like loop() is looping and your macro functions are executing independently of each other.. but that is not the case. They are passing control back and forth to each other and only one of them runs at a time.

    Ideally the way to do this is to not use time.sleep() at all, but to make note (in a variable) of the last time you changed the LED's.. and check in loop() to see how much time has passed.. if not enough time has passed, do nothing and check again next time. Once enough time has passed, flip the LEDs to the next state and reset your timer. That's the classic way to solve this kind of problem.. called a 'state machine' approach.

    When you call sleep() everything stops, so you want to avoid that.