Python - scheduler blocks interrupt - python

I am using a system based on a raspberry to control some stuff. At the moment I am just testing it with turning a led on and off.
My plan is: Press a button to open a valve. Press the button again to close it - but if the button is not pressed, close it after a set time. My current script is as follows: (I know that this will not turn the led off on the second press)
import RPi.GPIO as GPIO # Import Raspberry Pi GPIO library
import time,sched
s = sched.scheduler(time.time, time.sleep)
def button_callback(channel):
print("Button was pushed!")
print(time.time())
GPIO.output(18,GPIO.HIGH)
s.enter(10, 1, turnoff,argument='')
s.run()
def turnoff():
print "LED off"
print(time.time())
GPIO.output(18,GPIO.LOW)
btpin=22
ledpin=18
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(btpin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(ledpin,GPIO.OUT)
GPIO.add_event_detect(btpin,GPIO.RISING,callback=button_callback)
message = input("Press enter to quit\n\n")
GPIO.cleanup()
If I press the button and then leaves things alone, the led will turn off after 10 secs. But, if I press the button again immideately, nothing happens before the scheduler has finished, then a new press is registered. I had expected that the scheduler was spun of in the background so that when I pressed the button again, the callback would have ran again so I would have gotten the "Button was pushed" message (and everything happening afterwards would not have had any effect as GPIO 18 already was high and the scheduled call to turnoff would have happened after turnoff already had run.
Is it possible to use the sched library to do what I want or do I have to use some other techniques? I know I can either do it the simple way, by looping looking for a pressed button rather than registering a callback, or probably a more complicated way by creating a new thread that will pull the GPIO down after a given time - but is there something I have not understood in sched or is there some other library that gives me what I want - a way to tell python do do something a bit in the future without interfering with what else is going on.
(I do not need a very accurate timing - and also, this is just a part of what I intend to make a more complex control system, I manage to do exactly what I want using an arduino, but that will limit the further development)

Thanks to the tip from #stovfl, I rewrote the first part of my code:
import time,threading
def button_callback(channel):
pin=18
print("Button was pushed!")
print(time.time())
GPIO.output(pin,GPIO.HIGH)
t = threading.Timer(10.0, turnoff)
t.start()
and it works just like I wanted

Related

GPIO event detect not giving output when button pressed

The following python script is supposed to wait for a button press, print a button press message, and then exit.
However, when I press the button nothing is printed. Then when I press the enter key, the script prints "Button push detected" then stops.
How might I fix this code so that 'Button push detected' is printed when I push the button?
I followed a tutorial to make this code:
#Button input detection
#by
#Start date: 11th February 2021
#End date: 11th February 2021
#Importing GPIO andtime libraries as per usual with a python script making use of RPi GPIO
import RPi.GPIO as GPIO
import time
#Callback function to print 'Button push detected' when called
def button_callback(channel):
print("Button push detected")
#Disabling annoying GPIO warnings and setting GPIO mode to board
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(11, GPIO.IN, pull_up_down=GPIO.PUD_UP)
#GPIO event detector that detects a rising edge on pin 11
GPIO.add_event_detect(11,GPIO.FALLING,callback=button_callback)
#Command that does not stop until someone presses enter
quit = input("Press enter to quit")
#Clean up the pins used
GPIO.cleanup()
The problem is that your script needs a loop that waits until the button is pressed so that when the button press is detected, your script will be able to react to the event.
Right now your script sets the event detection but then sits waiting for input and as soon as the input is made, your script exits.
See this Raspberry Pi forum post, https://www.raspberrypi.org/forums/viewtopic.php?t=201747
which has a program for lighting an LED on and off with button presses.
However it looks to me like you would probably need to make a change to the call back function to raise an exception rather than light the LED if you want the script to end when you press the button.
See below the modified program with some additional annotation along with the LED light/unlight commented out.
However this modified program uses a busy loop that just runs continuously until the button is pressed. Using a busy loop, while it works for simple programs such as yours, is generally not a good idea.
An alternative is to use the GPIO.wait_for_edge() function that will pause the script until the button is pressed. See Raspberry Pi StackExchange - Pausing code execution till a button is pressed which removes the busy loop.
First the busy loop version.
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
btn_input = 4; # button to monitor for button presses.
LED_output = 17; # LED to light or not depending on button presses.
# GPIO btn_input set up as input.
GPIO.setup(btn_input, GPIO.IN)
# GPIO.setup(LED_output, GPIO.OUT)
# handle the button event
def buttonEventHandler_rising (pin):
# turn LED on
# GPIO.output(LED_output,True)
raise Exception('button pressed')
def buttonEventHandler_falling (pin):
# turn LED off
# GPIO.output(LED_output,False)
raise Exception('button released')
# set up the event handlers so that when there is a button press event, we
# the specified call back is invoked.
# for your purposes you may only want to detect the falling
# indicating that the button was released.
# GPIO.add_event_detect(btn_input, GPIO.RISING, callback=buttonEventHandler_rising)
GPIO.add_event_detect(btn_input, GPIO.FALLING, callback=buttonEventHandler_falling)
# we have now set our even handlers and we now need to wait until
# the event we have registered for actually happens.
# This is an infinite loop that waits for an exception to happen and
# and when the exception happens, the except of the try is triggered
# and then execution continues after the except statement.
try:
while True : pass
except:
GPIO.cleanup()
See as well the explanations as to what is happening in these forum posts
https://www.raspberrypi.org/forums/viewtopic.php?t=128510
https://www.raspberrypi.org/forums/viewtopic.php?t=141520
And this article about exceptions in python. https://realpython.com/python-exceptions/

Raspberry Pi Interrupts Python (GPIO Library)

I have a little problem with my little raspberry project, I have hooked up an LCD screen and a bunch of buttons to it, from my university course about micro-controllers I've learned that interrupts always trigger, no matter where the code is (but it was an actual microprocessor written in C). I found an "equivalent" in python, GPIO library. Normally a program is in a infinite loop waiting for an interrupt.
GPIO.add_event_detect(4, GPIO.RISING, callback=interrupt, bouncetime=200)
I set them up this way, they all go to the same method and check which button was pressed and run another method that I want (e.g one is for displaying time, another for IP address etc). A thing is, one of those methods I've done is in an infinite loop that another button press should break, but from this point a method
while not GPIO.event_detected(4):
doesn't work (calling this method in any other doesn't either if its in this loop, and all other button that I have set up will not react at all too), if it was my default while loop it does tho. I don't have much experience in either micro-controllers and python, its just hobby thing at the moment. If needed I'll share my entire code but I think its quite tangled.
Ok I am providing a simplified example, same same thing happens as in my original code. After an interrupt happens, buttons will not react, doesn't matter if I use while not GPIO.event_detected(19) or GPIO.add_event_callback(26, callback=second_interrupt).
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setup(26, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Button 1
GPIO.setup(19, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Button 2
def interrupt(channel):
print "interrupt"
if channel == 26:
print "in loop"
GPIO.add_event_callback(26, callback=second_interrupt) #Trying to use this won't trigger second interrupt
while not GPIO.event_detected(19): #and this wont ever trigger
time.sleep(1)
print "inside loop"
def second_interrupt():
print "second interrupt"
GPIO.add_event_detect(26, GPIO.RISING, callback=interrupt, bouncetime=200) # add rising edge detection on a channel
GPIO.add_event_detect(19, GPIO.RISING, callback=interrupt, bouncetime=200) # add rising edge detection on a channel
while (True):
time.sleep(1)
if GPIO.event_detected(19): #this version works if I wont enter an interrupt first
second_interrupt()
There are several things going on in your code. I suspect that what's really causing the problem is that you need to exit the interrupt handler before another interrupt callback can be triggered...but there is also a confusing mix of callback-based handlers and the GPIO.event_detected method.
I think you can simplify things by performing less manipulation of your interrupt configuration. Just have a state variable that starts at 0, increment it to 1 on the first interrupt, so the next time the interrupt method is called you know it's the second interrupt. No need to try setting multiple handlers like that.
Keeping in mind that I don't actually know what you're trying to do...I imagine something like this:
import RPi.GPIO as GPIO
import time
state = 0
GPIO.setmode(GPIO.BCM)
GPIO.setup(26, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(19, GPIO.IN, pull_up_down=GPIO.PUD_UP)
def interrupt_handler(channel):
global state
print("interrupt handler")
if channel == 19:
if state == 1:
state = 0
print("state reset by event on pin 19")
elif channel == 26:
if state == 0:
state = 1
print("state set by event on pin 26")
GPIO.add_event_detect(26, GPIO.RISING,
callback=interrupt_handler,
bouncetime=200)
GPIO.add_event_detect(19, GPIO.RISING,
callback=interrupt_handler,
bouncetime=200)
while (True):
time.sleep(0)

Python - wxPython Sequence of Command Execution

I have a properly working code, I understand I am not pasting enough code - but I will explain each command with proper comments. My question here is why is the code behaving rather than what I expected it to behave.
My code:
def OnReset(self, event): # A function event which works after a button is pressed
self.reset_pump.Disable() # Disables the button so it is clicked
self.WriteToController([0x30],'GuiMsgIn') # Sends the reset command
self.flag_read.set() # Set Event of the thread
time.sleep(0.25)
self.tr.join() # Joining a Thread
self.MessageBox('Pump RESET going on Click OK \n')
# Having the above step is useful
# The question I have is based on the commands from here:
self.offset_text_control.Clear()
self.gain_text_control.Clear()
self.firmware_version_text_control.Clear()
self.pump_rpm_text_control.Clear()
self.pressure_text_control.Clear()
self.last_error_text_control.Clear()
self.error_count_text_control.Clear()
self.pump_model_text_control.Clear()
self.pump_serial_number_text_control.Clear()
self.on_time_text_control.Clear()
self.job_on_time_text_control.Clear()
# The above commands clear various widgets on my GUI.
self.ser.close() # Closes my serial connection to MCU
time.sleep(5)
self.OnCorrectComPort(event) # An external function which gets executed. This function has a Message BOX - which says - PORT IS OPENED.
return
I expect, once the thread is joined - my commands will clear the GUI. Then close the serial connection using (ser.close()). Then the self.OnCorrectComPort(event) gets executed.
This is what I am seeing: Thread joins with tr.join() then self.OnCorrecComPort(event) gets executed as I can see the Message box with "PORT OPENED" appears, I click OK, then my GUI gets CLEARED. To my understanding this is wrong, anyone please correct me.
The problem is that you're calling time.sleep(5) and self.OnCorrectComPort() in the callback, before returning to the mainloop where the events will be processed.
The widgets will not reflect the effects of your Clear calls until you exit of the callback into the wx mainloop.
What happens is, the routines you call are executed (takes several seconds because of the time.sleep call, then wx gets to process the graphical commands, and the widgets are cleared at this very moment (which is too late and the GUI seems stuck with the previous state)
If you want it the other way round, you can use wx.CallAfter() to leave wx a chance to process its events before you call your routines.
In your case, since you want to wait 5 seconds, the risk is to freeze your interface again. It's even better to call wx.CallLater() with a 5 second delay in that case, leaving the time to wx to refresh all the widgets.
Modified code:
def OnReset(self, event): # A function event which works after a button is pressed
self.reset_pump.Disable() # Disables the button so it is clicked
self.WriteToController([0x30],'GuiMsgIn') # Sends the reset command
self.flag_read.set() # Set Event of the thread
time.sleep(0.25)
self.tr.join() # Joining a Thread
self.MessageBox('Pump RESET going on Click OK \n')
# Having the above step is useful
# The question I have is based on the commands from here:
self.offset_text_control.Clear()
self.gain_text_control.Clear()
self.firmware_version_text_control.Clear()
self.pump_rpm_text_control.Clear()
self.pressure_text_control.Clear()
self.last_error_text_control.Clear()
self.error_count_text_control.Clear()
self.pump_model_text_control.Clear()
self.pump_serial_number_text_control.Clear()
self.on_time_text_control.Clear()
self.job_on_time_text_control.Clear()
# The above commands clear various widgets on my GUI.
self.ser.close() # Closes my serial connection to MCU
# will call calledAfter after 5 seconds
wx.CallLater(5000,self.calledAfter,[ser,event])
def calledAfter(self,ser,event):
self.OnCorrectComPort(event) # An external function which gets executed. This function has a Message BOX - which says - PORT IS OPENED.

Tkinter .after runs forever and .mainloop never runs

I'm creating a python program on my RPi3 that changes the frequency of a GPIO pin depending on a Tkinter scale.
Here is my code:
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
from Tkinter import *
import time
freq = 1.0
master = Tk()
def update():
period = 1.0/float(freq)
GPIO.output(8, GPIO.HIGH)
time.sleep(period/2.0)
GPIO.output(8, GPIO.LOW)
time.sleep(period/2.0)
master.after(0, update)
scale = Scale(master, from_=1, to=20000, orient=HORIZONTAL, variable=freq)
scale.pack()
GPIO.setup(8, GPIO.OUT)
master.after(0, update)
master.mainloop()
GPIO.cleanup()
For some reason, master.after(0, update) runs forever and master.mainloop() never runs. I can tell because the scale never shows up and pin 8 turns on for half a second, then turns off for half a second, and the cycle repeats. If I press Ctrl+C then master.after(0, update) stops running and master.mainloop() starts running, the scale appears, but nothing happens when I drag the slider left and right.
I ran the program by typing sudo python tone.py in the terminal then pressing enter.
Fix/Alternative?
Processing events
You are making two somewhat common mistakes: you shouldn't do after(0, ...), and you shouldn't call sleep.
after(0, ...) means that every time you process the event, you immediately add another event. The event loop never has a chance to process other events in the queue, including events to handle the slider, screen updates, etc.
When you call sleep, the GUI does exactly that: it sleeps. While it is sleeping it can't process any events.
The solution is to use only after with a reasonable time span, and not call sleep at all.
For example:
def update():
...
# set the pin high
GPIO.output(8, GPIO.HIGH)
# set the pin low in half the period
master.after(period/2, GPIO.output, 8, GPIO.LOW)
# do this again based on the period
master.after(period, update)
Another way, if you continually want to toggle the pin every half second, would be this:
def update(value=GPIO.HIGH):
GPIO.output(8, value)
next_value = GPIO.LOW if value == GPIO.HIGH else GPIO.HIGH
master.after(500, update, next_value)
Using the slider
When you use the variable attribute of the slider, the variable must be an instance of one of the special tkinter variables, such as IntVar. You will then need to call get or set to get or set the value.
For example:
freq = IntVar()
freq.set(1)
def update(value=GPIO.HIGH):
period = 1.0/float(freq.get())
...
mainloop() is running, but Tkinter won't update views unless it is idle. KeyboardInterrupt will tell it to cleanup, at which point it finishes its event queue (which includes updating interface) and exits.
Solution: Give mainloop time to be idle- you really just need to change your after(0, update) to have some milliseconds inside OR tell master to .update_idletasks() to update the GUI.
A slightly better solution would be to make your high/low parts into their own functions which call each after the needed delay- sleeping in the mainloop gui is a bad idea, as your GUI cannot update whatsoever if the program is sleeping. (nor can it take input et al.) You would have millisecond frames to change input before it updated again, while using two functions that call each other after the chosen milliseconds would let you adjust the timings et al while it's waiting to flip to the other on/off.

Python - How do I continuously repeat a sequence without a While loop and still be able to stop the sequence at any time

I have a Raspberry Pi with the Piface adaptor board. I have made a GUI which controls the LED's on the Piface board.
I wrote a small piece of code to make the LED's run up and down continuously, like Knight Riders car, using a While loop.
I then wrote another piece of code that created a GUI. In the GUI is a button that starts the LED's running up and down continuously with the While loop piece of code.
What I want to do is to have that GUI button start the LED running sequence, and then the same button stop the sequence at any time.
I do understand that the code is sitting/stuck in the While loop. And hence any buttons in the GUI are not going to have an effect.
So is there a better way of doing it? Any pointers would be appreciated.
Thanks in advance.
Another option is to run the LED while loop in a separate thread. Like in
the next code. The while loop is stopped by toggling the shared led_switch
variable.
"""
blinking LED
"""
import tkinter as tk
import threading
import time
led_switch=False
def start_stop():
global led_switch
led_switch=not led_switch
if led_switch:
t=threading.Thread(target=LED)
t.start()
def LED():
while led_switch:
print('LED on')
time.sleep(1)
print('LED off')
time.sleep(1)
root=tk.Tk()
button=tk.Button(root,command=lambda: start_stop(),text='start/stop')
button.pack()
tk.mainloop()
If you have a while loop and a GUI you can use generators to still use the loop and let the GUI run properly.
I sketch the Idea here and create an example for the Tkinter GUI.
You want to write your code as a loop and still use it in a GUI:
from Tkinter import *
from guiLoop import guiLoop # https://gist.github.com/niccokunzmann/8673951
#guiLoop
def led_blink(argument):
while 1:
print("LED on " + argument)
yield 0.5 # time to wait
print("LED off " + argument)
yield 0.5
t = Tk()
led_blink(t, 'shiny!') # run led_blink in this GUI
t.mainloop()
Output while the GUI is responsive:
LED on shiny!
LED off shiny!
LED on shiny!
LED off shiny!
...
Sadly Tkinter is the only GUI I know and it is a bad example because you can always update the GUI in your loop with the update() method of GUI elements:
root = Tk()
while 1:
print("LED on")
t = time.time() + 0.5
while t > time.time(): root.update()
print("LED off")
t = time.time() + 0.5
while t > time.time(): root.update()
But with such a guiLoop you can have multiple loops:
t = Tk()
led_blink(t, 'red')
led_blink(t, 'blue')
led_blink(t, 'green')
t.mainloop()
Here are some examples for starting and stopping the loop with a button.
If you're using Tkinter, there's a very easy pattern for running a loop. Given that the UI (in just about every UI toolkit) is already running an infinite loop to process events, you can leverage this to run code periodically.
Let's assume you have a python object "led" which has a method for toggling it on and off. You can have it switch from on to off every 100ms with something as simple as these three lines of code:
def blink(led):
led.toggle()
root.after(100, blink, led)
The above code will run forever, causing the led to blink every 100ms. If you want to be able to start and stop the blinking with a button, introduce a flag:
def blink(led):
if should_blink:
led.toggle()
root.after(100, blink, led)
When you set the toggle to True, the led will start blinking. When it's False, it will stop blinking.
The main thing to take away from this is that you already have an infinite loop running, so there's no need to create one of your own, and no need to use something as complex as threading. Simply create a function that does one frame of animation, or calls some function or does some unit of work, then have the function request that it be run again in the future. How far in the future defines how fast your animation or blink will run.

Categories

Resources