2

I have a raspberry PI 2. With a relay board, what i use to for a switch sequence (like a traffic light). I use a tool, called "webiopi" what create buttons on a website. When the button is clicked the function of the python script below is started.

What i want is to break out of the loop (or pause it) when another button is clicked. However, as long this loop is running, the tool don't look at the webpage

A kind of similar question is asked here Exiting a continuous loop in python via webiopi but this is for a single event and the solution doesn't work in my case.

Question is. How can I make this script look at a button what is clicked (can be a gpio switch as well) while the loop is running

GPIO_nek=11 
GPIO_schouder=12
GPIO_rug1=8
GPIO_ONOFF=18
interval1 = 2
interval2 = 4
for x in range(0, 20):
    GPIO.digitalWrite(GPIO_nek, GPIO.LOW)
    time.sleep(interval1)
    GPIO.digitalWrite(GPIO_schouder, GPIO.LOW)
    time.sleep(interval1)
    GPIO.digitalWrite(GPIO_nek, GPIO.HIGH)
    time.sleep(interval1)
    GPIO.digitalWrite(GPIO_rug1, GPIO.LOW)
    time.sleep(interval2)
    GPIO.digitalWrite(GPIO_schouder, GPIO.HIGH)
    if (GPIO.digitalRead(GPIO_ONOFF) == GPIO.LOW):
        GPIO.digitalWrite(GPIO_ONOFF, GPIO.HIGH)
        break
Community
  • 1
  • 1
Richard de Ree
  • 2,329
  • 4
  • 30
  • 47

2 Answers2

2

When monitoring a real time event such as sensors or your button your best solution will be setting up a separate thread or process that contains nothing but an infinite loop that watches the resource and sets a flag when something interesting happens.

The example below sets up a process that automatically takes a picture on the RPI aprox. every minute.

#!/usr/bin/python
#Threading Prototype - Running a background thread that will take
#  pictures at the appropriate time
#------------------------------------------------------------------

from multiprocessing import Queue
from multiprocessing import Process
from time import sleep
from datetime import datetime
from datetime import timedelta
from subprocess import call 


#Doing the work ---------------------------------------------------

#Global Variables ---
messages = Queue()
start_time = datetime.now()

#Returns number of Milliseconds since start of program
def milliSinceStart():
    global start_time
    dt = datetime.now() - start_time
    ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0
    return ms

#Process Methods --------------------------------------------------
def processMessages():
    print "Message Processor Launched"
    while True:
        print messages.get()  #should halt until message in queue
        sleep(0.1) #sleep for a tick just to avoid a run away process

def processPicutres():
    print "Picture Taker Launched"
    pictureCycleStart = milliSinceStart()
    index = 0

    while True:
        if milliSinceStart() - pictureCycleStart > 10000: #once a minute
            a = "blip" + str(index) + ".jpg"
            b = "raspistill -n -t 100 -o " + a
            messages.put("Click")
            call ([b], shell=True)
            messages.put("picture taken - " + b)
            index = index + 1

            pictureCycleStart = milliSinceStart()


        sleep(0.1) #wait a tick -- don't hog processor time




def main():
    print "MultiProcessing Prototype"

    print "Launching message process"
    Process(target=processMessages).start()
    print "Back from launch"

    print "Launching picture taking process"
    Process(target=processPicutres).start()
    print "Back from launch"


    cycleStart = milliSinceStart()
    index = 0
    while True:
        if milliSinceStart() - cycleStart > 1000:
            messages.put("Tick " + str(index))
            cycleStart = milliSinceStart()
            index = index + 1


if __name__ == "__main__":
      main()

The main method launches the Messaging and Picture processes and then sets up its own little infinite loop that does nothing more that display the message "Tick" every second. The picture process sets up a separate infinite loop, watching the clock and taking a picture periodically. The Message process monitors the picture process (again, an infinite loop) and when it detects that a picture has been taken, it outputs the fact to the screen.

The important part of this for your purpose is the message queue. The process queue is what is allowing the Picture and Message processes to communicate.

And because the task take place in different processes, it matters not if one process pauses as the others are always active. If you set up a button monitor process you can be checking message queue for this fact and halting your main program when the button is pressed. This pause in the main program would not effect the button process which could then pick up on the fact that the button is pressed again.

codingCat
  • 2,396
  • 4
  • 21
  • 27
1

If the button is the GPIO switch as you mentioned at the end of the question, instead of the webpage button, then you can make use of an inbuilt GPIO interrupt function that saves your computer the resouces of constant polling:

import RPi.GPIO as GPIO
from threading import Event  # We'll use it like time.sleep, but we can interrupt it.
GPIO_nek=11 
GPIO_schouder=12
GPIO_rug1=8
GPIO_ONOFF=18
interval1 = 2
interval2 = 4
GPIO.setup(GPIO_ONOFF, GPIO.IN, pull_up_down=GPIO.PUD_UP)

done = False  # loop control
timer = Event()
def quit_loop():  # Called by inbuilt threaded interrupt
    global done
    done = True
    timer.set()  # Interrupt the waiting

GPIO.add_event_detect(GPIO_ONOFF, GPIO.FALLING, callback=quit_loop, bouncetime=300) # Setup interrupt to call quit_loop

Because you're using this to break out of a loop, you want to shorten that loop to a single process:

tasks = [
(GPIO_nek, GPIO.LOW, interval1),
(GPIO_schouder, GPIO.LOW, interval1),
(GPIO_nek, GPIO.HIGH, interval1),
(GPIO_rug1, GPIO.LOW, interval2),
(GPIO_schouder, GPIO.HIGH, 0) ]

for pin, level, interval in tasks * 20:  # Above you ran it 20 times, this notation keeps it in a single loop to break our o
    if not done:
        GPIO.digitalWrite(pin, level)
        timer.wait(interval)
    else:
        timer.clear()
        break

By using the threading Event().wait() and .set() instead of the standard time.sleep() you won't even have to wait for the sleep interval to finish.

Paul Brown
  • 2,235
  • 12
  • 18
  • That's an awesome way to solve this problem Paul. Thank you so much for this suggestion. I will try this and put the final solution here. – Richard de Ree Apr 09 '16 at 06:37